Revision 562b53c8f06c03b62488777148f634f5c53de32d authored by David Bryant on 10 August 2024, 22:21:05 UTC, committed by David Bryant on 10 August 2024, 22:21:05 UTC
1 parent ebbf7c5
wvtest.c
////////////////////////////////////////////////////////////////////////////
// **** WAVPACK **** //
// Hybrid Lossless Wavefile Compressor //
// Copyright (c) 1998 - 2024 David Bryant. //
// All Rights Reserved. //
// Distributed under the BSD Software License (see license.txt) //
////////////////////////////////////////////////////////////////////////////
// wvtest.c
// This is the main module for the WavPack command-line library tester.
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>
// Threading support is required for wvtest (although it can still test
// a libwavpack that's NOT built with threading support).
#ifndef ENABLE_THREADS
#define ENABLE_THREADS
#endif
#include "../src/wavpack_local.h" // for threading typedefs and macros
#include "utils.h" // for PACKAGE_VERSION, etc.
#include "md5.h"
#define CLEAR(destin) memset (&destin, 0, sizeof (destin));
#ifndef M_PI
#define M_PI 3.14159265358979323
#endif
static const char *sign_on = "\n"
" WVTEST libwavpack Tester/Exerciser for WavPack %s Version %s\n"
" Copyright (c) 2024 David Bryant. All Rights Reserved.\n\n";
static const char *version_warning = "\n"
" WARNING: WVTEST using libwavpack version %s, expected %s (see README)\n\n";
static const char *usage =
" Usage: WVTEST --default|--exhaustive [-options]\n"
" WVTEST --seektest[=n] file.wv [...] (n=runs per file, def=1)\n\n"
" Options: --default = perform the default test suite\n"
" --exhaustive = perform the exhaustive test suite\n"
" --short = perform shorter runs of each test\n"
" --long = perform longer runs of each test\n"
" --no-decode = skip the decoding process\n"
" --no-extras = skip the \"extra\" modes\n"
" --no-hybrid = skip the hybrid modes\n"
" --no-floats = skip the float modes\n"
" --no-lossy = skip the lossy modes\n"
" --no-speeds = skip the speed modes (fast, high, etc.)\n"
" --help = display this message\n"
" --version = write the version to stdout\n"
" --threads[=n] = use multiple threads, optional 'n' must\n"
" be 1 - 12, 1 = single thread only\n"
" --write=n[-n][,...] = write specific test(s) (or range(s)) to disk\n\n"
" Web: Visit www.wavpack.com for latest version and info\n";
#define TEST_FLAG_EXTRA_MODE(x) ((x) & TEST_FLAG_EXTRA_MASK)
#define TEST_FLAG_EXTRA_MASK 0x7
#define TEST_FLAG_FLOAT_DATA 0x8
#define TEST_FLAG_WRITE_FILE 0x10
#define TEST_FLAG_DEFAULT 0x20
#define TEST_FLAG_EXHAUSTIVE 0x40
#define TEST_FLAG_NO_FLOATS 0x80
#define TEST_FLAG_NO_HYBRID 0x100
#define TEST_FLAG_NO_EXTRAS 0x200
#define TEST_FLAG_NO_LOSSY 0x400
#define TEST_FLAG_NO_SPEEDS 0x800
#define TEST_FLAG_STORE_FLOAT_AS_INT32 0x1000
#define TEST_FLAG_STORE_INT32_AS_FLOAT 0x2000
#define TEST_FLAG_IGNORE_WVC 0x4000
#define TEST_FLAG_NO_DECODE 0x8000
#define TEST_FLAG_INT32_FILL_LOW_BITS 0x10000
static int run_test_size_modes (int wpconfig_flags, int test_flags, int base_minutes);
static int run_test_speed_modes (int wpconfig_flags, int test_flags, int bits, int num_chans, int num_seconds);
static int run_test_extra_modes (int wpconfig_flags, int test_flags, int bits, int num_chans, int num_seconds);
static int run_test (int wpconfig_flags, int test_flags, int bits, int num_chans, int num_seconds);
#define NUM_WRITE_RANGES 10
static struct { int start, stop; } write_ranges [NUM_WRITE_RANGES];
int number_of_ranges;
static int worker_threads;
enum generator_type { noise, tone };
struct audio_generator {
enum generator_type type;
union {
struct noise_generator {
double sum1, sum2, sum2p; // these are changing
double factor, scalar; // these are constant
} noise_cxt;
struct tone_generator {
int sample_rate, samples_per_update; // these are constant
int high_frequency, low_frequency; // these are constant
double angle, velocity, acceleration; // these are changing
int samples_left;
} tone_cxt;
} u;
};
static int seeking_test (char *filename, int32_t test_count);
static void tone_generator_init (struct audio_generator *cxt, int sample_rate, int low_freq, int high_freq);
static void noise_generator_init (struct audio_generator *cxt, double factor);
static void audio_generator_run (struct audio_generator *cxt, float *samples, int num_samples);
static void mix_samples_with_gain (float *destin, float *source, int num_samples, int num_chans, double initial_gain, double final_gain);
static void truncate_float_samples (float *samples, int num_samples, int bits);
static void float_to_integer_samples (float *samples, int num_samples, int bits);
static void float_to_32bit_integer_samples (float *samples, int num_samples, int test_flags);
static void *store_samples (void *dst, int32_t *src, int qmode, int bps, int count);
static double frandom (void);
typedef struct {
uint32_t buffer_size, bytes_written, bytes_read, first_block_size;
volatile unsigned char *buffer_base, *buffer_head, *buffer_tail;
int push_back, done, error, empty_waits, full_waits;
wp_condvar_t cond_read, cond_write;
wp_mutex_t mutex;
FILE *file;
} StreamingFile;
typedef struct {
StreamingFile *wv_stream, *wvc_stream;
unsigned char md5_decoded [16];
uint32_t sample_count;
int num_errors;
} WavpackDecoder;
static void initialize_stream (StreamingFile *ws, int buffer_size);
static int write_block (void *id, void *data, int32_t length);
static void flush_stream (StreamingFile *ws);
static void free_stream (StreamingFile *ws);
static WavpackStreamReader freader;
//////////////////////////////////////// main () function for CLI //////////////////////////////////////
int main (argc, argv) int argc; char **argv;
{
int wpconfig_flags = CONFIG_MD5_CHECKSUM | CONFIG_OPTIMIZE_MONO, test_flags = 0, base_minutes = 2, res = 0;
int seektest = 0;
// loop through command-line arguments
while (--argc) {
if (**++argv == '-' && (*argv)[1] == '-' && (*argv)[2]) {
char *long_option = *argv + 2, *long_param = long_option;
while (*long_param)
if (*long_param++ == '=')
break;
if (!strcmp (long_option, "help")) { // --help
printf ("%s", usage);
return 0;
}
else if (!strcmp (long_option, "version")) { // --version
printf ("wvtest %s\n", PACKAGE_VERSION);
printf ("libwavpack %s\n", WavpackGetLibraryVersionString ());
return 0;
}
else if (!strcmp (long_option, "short")) { // --short
base_minutes = 1;
}
else if (!strcmp (long_option, "long")) { // --long
base_minutes = 5;
}
else if (!strcmp (long_option, "default")) { // --default
test_flags |= TEST_FLAG_DEFAULT;
}
else if (!strcmp (long_option, "exhaustive")) { // --exhaustive
test_flags |= TEST_FLAG_EXHAUSTIVE;
}
else if (!strcmp (long_option, "no-extras")) { // --no-extras
test_flags |= TEST_FLAG_NO_EXTRAS;
}
else if (!strcmp (long_option, "no-hybrid")) { // --no-hybrid
test_flags |= TEST_FLAG_NO_HYBRID;
}
else if (!strcmp (long_option, "no-lossy")) { // --no-lossy
test_flags |= TEST_FLAG_NO_LOSSY;
}
else if (!strcmp (long_option, "no-speeds")) { // --no-speeds
test_flags |= TEST_FLAG_NO_SPEEDS;
}
else if (!strcmp (long_option, "no-floats")) { // --no-floats
test_flags |= TEST_FLAG_NO_FLOATS;
}
else if (!strcmp (long_option, "no-decode")) { // --no-decode
test_flags |= TEST_FLAG_NO_DECODE;
}
else if (!strncmp (long_option, "write", 5)) { // --write
for (number_of_ranges = 0; *long_param && isdigit (*long_param) && number_of_ranges < NUM_WRITE_RANGES;) {
write_ranges [number_of_ranges].start = strtol (long_param, &long_param, 10);
if (*long_param == '-') {
long_param++;
if (isdigit (*long_param))
write_ranges [number_of_ranges].stop = strtol (long_param, &long_param, 10);
else
break;
}
else
write_ranges [number_of_ranges].stop = write_ranges [number_of_ranges].start;
number_of_ranges++;
if (*long_param == ',')
long_param++;
else
break;
}
if (*long_param || !number_of_ranges) {
printf ("syntax error in write specification!\n");
return 1;
}
else
test_flags |= TEST_FLAG_WRITE_FILE;
}
else if (!strncmp (long_option, "seektest", 8)) { // --seektest[=n]
if (*long_param)
seektest = strtol (long_param, NULL, 10);
else
seektest = 1;
if (seektest)
break;
}
else if (!strncmp (long_option, "threads", 7)) { // --threads
if (isdigit (*long_param)) {
// "worker_threads" doesn't include main thread, so subtract 1 from user value
worker_threads = strtol (long_param, &long_param, 10) - 1;
if (worker_threads < 0 || worker_threads > 11) {
printf ("specified thread count must be 1 - 12!\n");
return 1;
}
}
else
worker_threads = 4; // 4 is a good default for now
}
else {
printf ("unknown option: %s !\n", long_option);
return 1;
}
}
else {
printf ("unknown option: %s !\n", *argv);
return 1;
}
}
if (strcmp (WavpackGetLibraryVersionString (), PACKAGE_VERSION))
printf (version_warning, WavpackGetLibraryVersionString (), PACKAGE_VERSION);
else
printf (sign_on, VERSION_OS, WavpackGetLibraryVersionString ());
if (!seektest && !(test_flags & (TEST_FLAG_DEFAULT | TEST_FLAG_EXHAUSTIVE))) {
puts (usage);
return 1;
}
if (seektest) {
while (--argc)
if ((res = seeking_test (*++argv, seektest)))
break;
}
else {
printf ("\n\n ****** pure lossless ******\n");
res = run_test_size_modes (wpconfig_flags, test_flags, base_minutes);
if (res) goto done;
if (!(test_flags & TEST_FLAG_NO_HYBRID)) {
printf ("\n\n ****** hybrid lossless ******\n");
res = run_test_size_modes (wpconfig_flags | CONFIG_HYBRID_FLAG | CONFIG_CREATE_WVC, test_flags, base_minutes);
if (res) goto done;
if (!(test_flags & TEST_FLAG_NO_LOSSY)) {
printf ("\n\n ****** hybrid lossy ******\n");
res = run_test_size_modes (wpconfig_flags | CONFIG_HYBRID_FLAG, test_flags, base_minutes);
if (res) goto done;
printf ("\n\n ****** hybrid lossless (but ignore wvc on decode) ******\n");
res = run_test_size_modes (wpconfig_flags | CONFIG_HYBRID_FLAG | CONFIG_CREATE_WVC,
test_flags | TEST_FLAG_IGNORE_WVC, base_minutes);
if (res) goto done;
}
}
}
done:
if (res)
printf ("\ntest failed!\n\n");
else
printf ("\nall tests pass\n\n");
return res;
}
// Function to stress-test the WavpackSeekSample() API. Given the specified WavPack file, perform
// the specified number of seektest runs on that file. For each test run, a different, random
// seek interval is chosen. Note that MD5 sums are calculated for each chunk interval so we
// actually verify that every sample decoded is correct. For each test run, we decode the entire
// file 4 times over, on average.
static int seeking_test (char *filename, int32_t test_count)
{
char error [80];
int open_flags = OPEN_WVC | OPEN_DSD_NATIVE | OPEN_ALT_TYPES;
int64_t min_chunk_size = 256, total_samples, sample_count = 0;
char md5_string1 [] = "????????????????????????????????";
char md5_string2 [] = "????????????????????????????????";
int32_t *decoded_samples, num_chans, bps, test_index, qmode;
unsigned char md5_initial [16], md5_stored [16];
MD5_CTX md5_global, md5_local;
unsigned char *chunked_md5;
WavpackContext *wpc;
if (worker_threads)
open_flags |= worker_threads << OPEN_THREADS_SHFT;
wpc = WavpackOpenFileInput (filename, error, open_flags, 0);
printf ("\n-------------------- file: %s %s--------------------\n",
filename, (WavpackGetMode (wpc) & MODE_WVC) ? "(+wvc) " : "");
if (!wpc) {
printf ("seeking_test(): error \"%s\" opening input file \"%s\"\n", error, filename);
return -1;
}
num_chans = WavpackGetNumChannels (wpc);
total_samples = WavpackGetNumSamples64 (wpc);
bps = WavpackGetBytesPerSample (wpc);
qmode = WavpackGetQualifyMode (wpc);
if (total_samples < 2 || total_samples == -1) {
printf ("seeking_test(): can't determine file size!\n");
return -1;
}
if (qmode & QMODE_DSD_IN_BLOCKS) {
printf ("seeking_test(): can't handle blocked DSD audio (i.e., from .dsf files)!\n");
return -1;
}
// For very short files, reduce the minimum chunk size
while (min_chunk_size > 1 && total_samples / min_chunk_size < 256)
min_chunk_size /= 2;
for (test_index = 0; test_index < test_count; test_index++) {
uint32_t chunk_samples, total_chunks, chunk_count = 0, seek_count = 0;
chunk_samples = (uint32_t) (min_chunk_size + frandom () * min_chunk_size); // 256 - 511 (unless reduced)
total_chunks = (uint32_t) ((total_samples + chunk_samples - 1) / chunk_samples);
decoded_samples = malloc (sizeof (int32_t) * chunk_samples * num_chans);
chunked_md5 = malloc (total_chunks * 16);
if (!chunked_md5 || !decoded_samples) {
printf ("seeking_test(): can't allocate memory!\n");
return -1;
}
sample_count = chunk_count = 0;
MD5_Init (&md5_global);
// read the entire file, calculating the MD5 sums for the whole file and for each "chunk"
while (1) {
int samples = WavpackUnpackSamples (wpc, decoded_samples, chunk_samples);
if (!samples)
break;
if ((sample_count += samples) > total_samples || chunk_count >= total_chunks) {
printf ("seeking_test(): WavPack file is invalid or corrupt!\n");
return -1;
}
store_samples (decoded_samples, decoded_samples, qmode, bps, samples * num_chans);
MD5_Update (&md5_global, (unsigned char *) decoded_samples, bps * samples * num_chans);
MD5_Init (&md5_local);
MD5_Update (&md5_local, (unsigned char *) decoded_samples, bps * samples * num_chans);
MD5_Final (chunked_md5 + chunk_count * 16, &md5_local);
chunk_count++;
}
if (WavpackGetNumErrors (wpc)) {
printf ("seeking_test(): decoder reported %d errors!\n", WavpackGetNumErrors (wpc));
return -1;
}
if (total_samples != sample_count) {
printf ("seeking_test(): sample count is not correct!\n");
return -1;
}
if (total_chunks != chunk_count) {
printf ("seeking_test(): chunk count is not correct (not sure if this can happen)!\n");
return -1;
}
// The first time through the file we verify that the MD5 sum matches what's stored in the file
// (if one is stored there). On subsequent tests, we verify that the whole-file MD5 sum matches
// what we got the first time.
if (!test_index) {
int file_has_md5 = WavpackGetMD5Sum (wpc, md5_stored), i;
MD5_Final (md5_initial, &md5_global);
for (i = 0; i < 16; ++i) {
sprintf (md5_string1 + (i * 2), "%02x", md5_stored [i]);
sprintf (md5_string2 + (i * 2), "%02x", md5_initial [i]);
}
printf ("stored/actual sample count: %lld / %lld\n", (long long int) total_samples, (long long int) sample_count);
if (file_has_md5) printf ("stored md5: %s\n", md5_string1);
printf ("actual md5: %s\n", md5_string2);
if (WavpackGetMode (wpc) & MODE_LOSSLESS)
if (file_has_md5 && memcmp (md5_stored, md5_initial, sizeof (md5_stored))) {
printf ("seeking_test(): MD5 does not match MD5 stored in file!\n");
return -1;
}
}
else {
unsigned char md5_subsequent [16];
MD5_Final (md5_subsequent, &md5_global);
if (memcmp (md5_subsequent, md5_initial, sizeof (md5_stored))) {
printf ("seeking_test(): MD5 does not match MD5 read initially!\n");
return -1;
}
}
// Half the time, reopen the file. This lets us catch errors caused by seeking to locations
// that have never been decoded (at least not for this open call).
if (frandom() < 0.5) {
WavpackCloseFile (wpc);
wpc = WavpackOpenFileInput (filename, error, open_flags, 0);
if (!wpc) {
printf ("seeking_test(): error \"%s\" reopening input file \"%s\"\n", error, filename);
return -1;
}
}
chunk_count *= 4; // decode each chunk 4 times, on average
while (chunk_count) {
int start_chunk = 0, stop_chunk, current_chunk, num_chunks = 1;
start_chunk = (int) floor (frandom () * total_chunks);
if (start_chunk == total_chunks) start_chunk--;
// At a minimum, we very one chunk after the seek. However, we also random chose additional
// chunks to verify so that sometimes we verify lots of data after the seek.
while (start_chunk + num_chunks < (int) total_chunks && frandom () < 0.667)
num_chunks *= 2;
if (start_chunk + num_chunks > (int) total_chunks)
num_chunks = total_chunks - start_chunk;
stop_chunk = start_chunk + num_chunks - 1;
if (!WavpackSeekSample64 (wpc, (int64_t) start_chunk * chunk_samples)) {
printf ("seeking_test(): seek error!\n");
return -1;
}
for (current_chunk = start_chunk; current_chunk <= stop_chunk; ++current_chunk) {
int samples = WavpackUnpackSamples (wpc, decoded_samples, chunk_samples);
unsigned char md5_chunk [16];
if (!samples) {
printf ("seeking_test(): seek error!\n");
return -1;
}
store_samples (decoded_samples, decoded_samples, qmode, bps, samples * num_chans);
// if (frandom() < 0.0001)
// decoded_samples [(int) floor (samples * frandom())] ^= 1;
MD5_Init (&md5_local);
MD5_Update (&md5_local, (unsigned char *) decoded_samples, bps * samples * num_chans);
MD5_Final (md5_chunk, &md5_local);
if (memcmp (chunked_md5 + current_chunk * 16, md5_chunk, sizeof (md5_chunk))) {
printf ("seeking_test(): seek+decode error at %lld!\n", (long long int) current_chunk * chunk_samples);
return -1;
}
if (chunk_count)
chunk_count--;
else
break;
}
// display the "dot" every 10 seeks
if (++seek_count % 10 == 0) {
if (seek_count % 640) {
putchar ('.'); fflush (stdout);
}
else
puts (".");
}
}
printf ("\nresult: %u successful seeks on %u-sample boundaries\n", seek_count, chunk_samples);
if (!WavpackSeekSample (wpc, 0)) {
printf ("seeking_test(): rewind error!\n");
return -1;
}
free (chunked_md5);
free (decoded_samples);
}
WavpackCloseFile (wpc);
return 0;
}
// Given a WavPack configuration and test flags, run the various combinations of
// bit-depth and channel configurations. A return value of FALSE indicates an error.
static int run_test_size_modes (int wpconfig_flags, int test_flags, int base_minutes)
{
int res;
printf ("\n *** 8-bit, mono ***\n");
res = run_test_speed_modes (wpconfig_flags, test_flags, 8, 1, base_minutes*5*60);
if (res) return res;
if (test_flags & TEST_FLAG_EXHAUSTIVE) {
printf ("\n *** 16-bit, mono ***\n");
res = run_test_speed_modes (wpconfig_flags, test_flags, 16, 1, base_minutes*5*60);
if (res) return res;
}
printf ("\n *** 16-bit, stereo ***\n");
res = run_test_speed_modes (wpconfig_flags, test_flags, 16, 2, base_minutes*3*60);
if (res) return res;
if ((test_flags & TEST_FLAG_EXHAUSTIVE) && !(test_flags & TEST_FLAG_NO_FLOATS)) {
printf ("\n *** 16-bit (converted to float), stereo ***\n");
res = run_test_speed_modes (wpconfig_flags, test_flags | TEST_FLAG_FLOAT_DATA, 16, 2, base_minutes*3*60);
if (res) return res;
}
printf ("\n *** 24-bit, 5.1 channels ***\n");
res = run_test_speed_modes (wpconfig_flags, test_flags, 24, 6, base_minutes*60);
if (res) return res;
if (test_flags & TEST_FLAG_EXHAUSTIVE) {
if (!(test_flags & TEST_FLAG_NO_FLOATS)) {
printf ("\n *** 24-bit (converted to float), 5.1 channels ***\n");
res = run_test_speed_modes (wpconfig_flags, test_flags | TEST_FLAG_FLOAT_DATA, 24, 6, base_minutes*60);
if (res) return res;
}
printf ("\n *** 32-bit integer (converted from float), 5.1 channels ***\n");
res = run_test_speed_modes (wpconfig_flags, test_flags, 32, 6, base_minutes*60);
if (res) return res;
printf ("\n *** 32-bit integer, 5.1 channels ***\n");
res = run_test_speed_modes (wpconfig_flags, test_flags | TEST_FLAG_INT32_FILL_LOW_BITS, 32, 6, base_minutes*60);
if (res) return res;
if (!(test_flags & TEST_FLAG_NO_FLOATS)) {
printf ("\n *** 32-bit float stored as integer (pathological), 5.1 channels ***\n");
res = run_test_speed_modes (wpconfig_flags, test_flags | TEST_FLAG_STORE_FLOAT_AS_INT32, 32, 6, base_minutes*60);
if (res) return res;
if (!(wpconfig_flags & CONFIG_HYBRID_FLAG)) {
printf ("\n *** 32-bit integer stored as float (pathological), 5.1 channels ***\n");
res = run_test_speed_modes (wpconfig_flags, test_flags | TEST_FLAG_STORE_INT32_AS_FLOAT, 32, 6, base_minutes*60);
if (res) return res;
}
}
}
if (!(test_flags & TEST_FLAG_NO_FLOATS)) {
printf ("\n *** 32-bit float, 5.1 channels ***\n");
res = run_test_speed_modes (wpconfig_flags, test_flags | TEST_FLAG_FLOAT_DATA, 32, 6, base_minutes*60);
if (res) return res;
}
return 0;
}
// Given a WavPack configuration and test flags, run the various combinations of
// speed modes (i.e, fast, high, etc). A return value of FALSE indicates an error.
static int run_test_speed_modes (int wpconfig_flags, int test_flags, int bits, int num_chans, int num_seconds)
{
int res;
if (!(test_flags & TEST_FLAG_NO_SPEEDS)) {
res = run_test_extra_modes (wpconfig_flags | CONFIG_FAST_FLAG, test_flags, bits, num_chans, num_seconds);
if (res) return res;
}
res = run_test_extra_modes (wpconfig_flags, test_flags, bits, num_chans, num_seconds);
if (res) return res;
if (!(test_flags & TEST_FLAG_NO_SPEEDS)) {
res = run_test_extra_modes (wpconfig_flags | CONFIG_HIGH_FLAG, test_flags, bits, num_chans, num_seconds);
if (res) return res;
}
if (!(test_flags & TEST_FLAG_NO_SPEEDS)) {
res = run_test_extra_modes (wpconfig_flags | CONFIG_VERY_HIGH_FLAG, test_flags, bits, num_chans, num_seconds);
if (res) return res;
}
return 0;
}
// Given a WavPack configuration and test flags, run the various combinations of "extra" modes (0-6).
// Note that except for the base mode (no extra), the "default" and "exhaustive" configurations do
// different extra modes. Combining the "default" and "exhaustive" configurations does all the extra
// modes. A return value of FALSE indicates an error.
static int run_test_extra_modes (int wpconfig_flags, int test_flags, int bits, int num_chans, int num_seconds)
{
int res;
res = run_test (wpconfig_flags, test_flags, bits, num_chans, num_seconds);
if (res) return res;
if (test_flags & TEST_FLAG_NO_EXTRAS)
return 0;
if (test_flags & TEST_FLAG_EXHAUSTIVE) {
res = run_test (wpconfig_flags, test_flags | TEST_FLAG_EXTRA_MODE (1), bits, num_chans, num_seconds);
if (res) return res;
}
if (test_flags & TEST_FLAG_DEFAULT) {
res = run_test (wpconfig_flags, test_flags | TEST_FLAG_EXTRA_MODE (2), bits, num_chans, num_seconds);
if (res) return res;
}
if (test_flags & TEST_FLAG_EXHAUSTIVE) {
res = run_test (wpconfig_flags, test_flags | TEST_FLAG_EXTRA_MODE (3), bits, num_chans, num_seconds);
if (res) return res;
}
if (test_flags & TEST_FLAG_EXHAUSTIVE) {
res = run_test (wpconfig_flags, test_flags | TEST_FLAG_EXTRA_MODE (4), bits, num_chans, num_seconds);
if (res) return res;
}
if (test_flags & TEST_FLAG_DEFAULT) {
res = run_test (wpconfig_flags, test_flags | TEST_FLAG_EXTRA_MODE (5), bits, num_chans, num_seconds);
if (res) return res;
}
if (test_flags & TEST_FLAG_EXHAUSTIVE) {
res = run_test (wpconfig_flags, test_flags | TEST_FLAG_EXTRA_MODE (6), bits, num_chans, num_seconds);
if (res) return res;
}
return 0;
}
// Given a WavPack configuration and test flags, actually run the specified test. This entails
// generating the actual audio test data, creating the "virtual" WavPack file and writing to it,
// and spawning the thread that will read the "virtual" file and do the decoding (which is obviously
// required to verify the entire encode/decode chain). For lossless modes, and MD5 hash is used to
// verify the result, otherwise the decoder is trusted to detect and report errors (although the
// total number of samples is verified).
#define BUFFER_SIZE 1000000
#define NUM_GENERATORS 6
struct audio_channel {
double audio_gain_hist [NUM_GENERATORS], audio_gain [NUM_GENERATORS], angle_offset;
int lfe_flag;
};
#define SAMPLE_RATE 44100
#define ENCODE_SAMPLES 128
#define DESTIN_SAMPLES (220672) // multiple of ENCODE_SAMPLES, long enough for temporal multithreading
#define NOISE_GAIN 0.6667
#define TONE_GAIN 0.3333
#ifdef _WIN32
static unsigned WINAPI decode_thread (LPVOID threadid);
#else
static void *decode_thread (void *threadid);
#endif
static int run_test (int wpconfig_flags, int test_flags, int bits, int num_chans, int num_seconds)
{
static int test_number;
double sequencing_angle = 0.0, speed = 60.0, width = 200.0, ratio, bps;
int lossless = !(wpconfig_flags & CONFIG_HYBRID_FLAG) || ((wpconfig_flags & CONFIG_CREATE_WVC) && !(test_flags & TEST_FLAG_IGNORE_WVC));
char md5_string1 [] = "????????????????????????????????";
char md5_string2 [] = "????????????????????????????????";
uint32_t total_encoded_bytes, total_encoded_samples;
struct audio_generator generators [NUM_GENERATORS];
int seconds = 0, samples = 0, wc = 0, chan_mask;
char *filename = NULL, mode_string [32] = "-";
struct audio_channel *channels;
float *source, *destin;
wp_thread_t thread;
WavpackContext *out_wpc;
WavpackConfig wpconfig;
StreamingFile wv_stream, wvc_stream;
WavpackDecoder wv_decoder;
unsigned char md5_encoded [16];
MD5_CTX md5_context;
int i, j, k;
if (wpconfig_flags & CONFIG_FAST_FLAG)
strcat (mode_string, "f");
else if (wpconfig_flags & CONFIG_HIGH_FLAG)
strcat (mode_string, "h");
else if (wpconfig_flags & CONFIG_VERY_HIGH_FLAG)
strcat (mode_string, "hh");
printf ("test %04d...", ++test_number); fflush (stdout);
MD5_Init (&md5_context);
noise_generator_init (&generators [0], 128.0);
tone_generator_init (&generators [1], SAMPLE_RATE, 20, 200);
noise_generator_init (&generators [2], 12.0);
tone_generator_init (&generators [3], SAMPLE_RATE, 200, 2000);
noise_generator_init (&generators [4], 1.75);
tone_generator_init (&generators [5], SAMPLE_RATE, 2000, 20000);
CLEAR (wpconfig);
CLEAR (wv_decoder);
CLEAR (wv_stream);
CLEAR (wvc_stream);
channels = malloc (num_chans * sizeof (*channels));
source = malloc (ENCODE_SAMPLES * sizeof (*source));
destin = malloc (DESTIN_SAMPLES * num_chans * sizeof (*destin));
if (!channels || !source || !destin) {
printf ("run_test(): can't allocate memory!\n");
exit (-1);
}
memset (channels, 0, num_chans * sizeof (*channels));
switch (num_chans) {
case 1:
channels [0].angle_offset = 0.0;
chan_mask = 0x4;
break;
case 2:
channels [0].angle_offset -= M_PI / 24.0;
channels [1].angle_offset += M_PI / 24.0;
chan_mask = 0x3;
break;
case 4:
channels [0].angle_offset -= M_PI / 24.0;
channels [1].angle_offset += M_PI / 24.0;
channels [2].angle_offset -= 23.0 * M_PI / 24.0;
channels [3].angle_offset += 23.0 * M_PI / 24.0;
chan_mask = 0x33;
break;
case 6:
channels [0].angle_offset -= M_PI / 24.0;
channels [1].angle_offset += M_PI / 24.0;
channels [3].lfe_flag = 1;
channels [4].angle_offset -= 23.0 * M_PI / 24.0;
channels [5].angle_offset += 23.0 * M_PI / 24.0;
chan_mask = 0x3F;
break;
default:
printf ("invalid channel count = %d\n", num_chans);
exit (-1);
}
if (!(test_flags & TEST_FLAG_NO_DECODE)) {
initialize_stream (&wv_stream, BUFFER_SIZE);
wv_decoder.wv_stream = &wv_stream;
}
else
initialize_stream (&wv_stream, 0);
if (test_flags & TEST_FLAG_WRITE_FILE) {
int i;
for (i = 0; i < number_of_ranges; ++i)
if (test_number >= write_ranges [i].start && test_number <= write_ranges [i].stop) {
filename = malloc (32);
if (!filename) {
printf ("run_test(): can't allocate memory!\n");
exit (-1);
}
sprintf (filename, "testfile-%04d.wv", test_number);
if (((wv_stream.file = fopen (filename, "w+b")) == NULL)) {
printf ("can't create file %s!\n", filename);
free_stream (&wv_stream);
return 1;
}
break;
}
}
if (wpconfig_flags & CONFIG_CREATE_WVC) {
if (!(test_flags & (TEST_FLAG_IGNORE_WVC | TEST_FLAG_NO_DECODE))) {
initialize_stream (&wvc_stream, BUFFER_SIZE);
wv_decoder.wvc_stream = &wvc_stream;
}
else
initialize_stream (&wvc_stream, 0);
if (filename) {
char *filename_c = malloc (strlen (filename) + 10);
strcpy (filename_c, filename);
strcat (filename_c, "c");
if ((wvc_stream.file = fopen (filename_c, "w+b")) == NULL) {
printf ("can't create file %s!\n", filename_c);
free_stream (&wv_stream);
free_stream (&wvc_stream);
return 1;
}
free (filename_c);
}
}
out_wpc = WavpackOpenFileOutput (write_block, &wv_stream, (wpconfig_flags & CONFIG_CREATE_WVC) ? &wvc_stream : NULL);
if (!(test_flags & TEST_FLAG_NO_DECODE))
wp_thread_create (thread, decode_thread, (void *) &wv_decoder);
if (test_flags & (TEST_FLAG_FLOAT_DATA | TEST_FLAG_STORE_INT32_AS_FLOAT)) {
wpconfig.float_norm_exp = 127;
wpconfig.bytes_per_sample = 4;
wpconfig.bits_per_sample = 32;
}
else {
wpconfig.bytes_per_sample = (bits + 7) >> 3;
wpconfig.bits_per_sample = bits;
// for 32-bit integers, set the new optimize flag in the "high" modes only
if (bits == 32 && (wpconfig_flags & (CONFIG_HIGH_FLAG | CONFIG_VERY_HIGH_FLAG)))
wpconfig_flags |= CONFIG_OPTIMIZE_32BIT;
}
if (test_flags & TEST_FLAG_EXTRA_MASK) {
sprintf (mode_string + strlen (mode_string), "x%c", '0' + (test_flags & TEST_FLAG_EXTRA_MASK));
wpconfig.xmode = test_flags & TEST_FLAG_EXTRA_MASK;
wpconfig_flags |= CONFIG_EXTRA_MODE;
}
wpconfig.sample_rate = SAMPLE_RATE;
wpconfig.num_channels = num_chans;
wpconfig.channel_mask = chan_mask;
wpconfig.flags = wpconfig_flags;
if (wpconfig_flags & CONFIG_HYBRID_FLAG) {
if (wpconfig_flags & CONFIG_CREATE_WVC) {
if (test_flags & TEST_FLAG_IGNORE_WVC) {
strcat (mode_string, "b4c");
wpconfig.bitrate = 4.0;
}
else {
strcat (mode_string, "b3c");
wpconfig.bitrate = 3.0;
}
}
else {
strcat (mode_string, "b5");
wpconfig.bitrate = 5.0;
}
}
if (worker_threads)
wpconfig.worker_threads = worker_threads;
WavpackSetConfiguration64 (out_wpc, &wpconfig, -1, NULL);
WavpackPackInit (out_wpc);
while (seconds < num_seconds) {
int destin_samples = 0;
while (destin_samples < DESTIN_SAMPLES && seconds < num_seconds) {
float *destin_ptr = destin + destin_samples * num_chans;
double translated_angle = cos (sequencing_angle) * 100.0;
double width_scalar = pow (2.0, -width);
for (k = 0; k < num_chans; ++k) {
channels [k].audio_gain [0] = pow (sin (translated_angle + channels [k].angle_offset - M_PI * 1.6667) + 1.0, width) * width_scalar * NOISE_GAIN;
channels [k].audio_gain [1] = pow (sin (translated_angle + channels [k].angle_offset - M_PI * 0.6667) + 1.0, width) * width_scalar * TONE_GAIN;
channels [k].audio_gain [2] = pow (sin (translated_angle + channels [k].angle_offset - M_PI * 0.3333) + 1.0, width) * width_scalar * NOISE_GAIN;
channels [k].audio_gain [3] = pow (sin (translated_angle + channels [k].angle_offset - M_PI * 1.3333) + 1.0, width) * width_scalar * TONE_GAIN;
channels [k].audio_gain [4] = pow (sin (translated_angle + channels [k].angle_offset - M_PI) + 1.0, width) * width_scalar * NOISE_GAIN;
channels [k].audio_gain [5] = pow (sin (translated_angle + channels [k].angle_offset) + 1.0, width) * width_scalar * TONE_GAIN;
}
memset (destin_ptr, 0, ENCODE_SAMPLES * num_chans * sizeof (*destin));
for (j = 0; j < NUM_GENERATORS; ++j) {
audio_generator_run (&generators [j], source, ENCODE_SAMPLES);
for (k = 0; k < num_chans; ++k) {
if (!channels [k].lfe_flag || j < 2)
mix_samples_with_gain (destin_ptr + k, source, ENCODE_SAMPLES, num_chans, channels [k].audio_gain_hist [j], channels [k].audio_gain [j]);
channels [k].audio_gain_hist [j] = channels [k].audio_gain [j];
}
}
sequencing_angle += 2.0 * M_PI / SAMPLE_RATE / speed * ENCODE_SAMPLES;
if (sequencing_angle > M_PI) sequencing_angle -= M_PI * 2.0;
if ((samples += ENCODE_SAMPLES) >= SAMPLE_RATE) {
samples -= SAMPLE_RATE;
++seconds;
if (!(wc & 1)) {
if (width > 1.0) width *= 0.875;
else if (width > 0.125) width -= 0.125;
else {
width = 0.0;
wc++;
}
}
else {
if (width < 1.0) width += 0.125;
else if (width < 200.0) width *= 1.125;
else wc++;
}
}
destin_samples += ENCODE_SAMPLES;
}
if (test_flags & TEST_FLAG_FLOAT_DATA) {
if (bits <= 25)
truncate_float_samples (destin, destin_samples * num_chans, bits);
else if (bits != 32) {
printf ("invalid bits configuration\n");
exit (-1);
}
}
else if (!(test_flags & TEST_FLAG_STORE_FLOAT_AS_INT32)) {
if (bits < 32)
float_to_integer_samples (destin, destin_samples * num_chans, bits);
else if (bits == 32)
float_to_32bit_integer_samples (destin, destin_samples * num_chans, test_flags);
else {
printf ("invalid bits configuration\n");
exit (-1);
}
}
WavpackPackSamples (out_wpc, (int32_t *) destin, destin_samples);
store_samples (destin, (int32_t *) destin, 0, wpconfig.bytes_per_sample, destin_samples * num_chans);
MD5_Update (&md5_context, (unsigned char *) destin, wpconfig.bytes_per_sample * destin_samples * num_chans);
}
WavpackFlushSamples (out_wpc);
MD5_Final (md5_encoded, &md5_context);
if (wpconfig.flags & CONFIG_MD5_CHECKSUM) {
WavpackStoreMD5Sum (out_wpc, md5_encoded);
WavpackFlushSamples (out_wpc);
}
WavpackCloseFile (out_wpc);
free (channels);
free (source);
free (destin);
if ((wpconfig_flags & CONFIG_CREATE_WVC) && !(test_flags & TEST_FLAG_IGNORE_WVC))
total_encoded_bytes = wv_stream.bytes_written + wvc_stream.bytes_written;
else
total_encoded_bytes = wv_stream.bytes_written;
total_encoded_samples = seconds * SAMPLE_RATE + samples;
ratio = total_encoded_bytes / ((float) total_encoded_samples * wpconfig.bytes_per_sample * num_chans);
bps = total_encoded_bytes * 8 / ((float) total_encoded_samples * num_chans);
flush_stream (&wv_stream);
flush_stream (&wvc_stream);
if (!(test_flags & TEST_FLAG_NO_DECODE))
wp_thread_join (thread);
if (!(test_flags & TEST_FLAG_NO_DECODE)) {
for (i = 0; i < 16; ++i) {
sprintf (md5_string1 + (i * 2), "%02x", md5_encoded [i]);
sprintf (md5_string2 + (i * 2), "%02x", wv_decoder.md5_decoded [i]);
}
if (wv_decoder.num_errors || wv_decoder.sample_count != total_encoded_samples ||
(lossless && memcmp (md5_encoded, wv_decoder.md5_decoded, sizeof (md5_encoded)))) {
printf ("\n---------------------------------------------\n");
printf ("enc/dec sample count: %u / %u\n", total_encoded_samples, wv_decoder.sample_count);
printf ("encoded md5: %s\n", md5_string1);
printf ("decoded md5: %s\n", md5_string2);
printf ("reported decode errors: %d\n", wv_decoder.num_errors);
printf ("---------------------------------------------\n");
return wv_decoder.num_errors + 1;
}
}
free_stream (&wv_stream);
free_stream (&wvc_stream);
printf ("pass (%8s, %.2f%%, %.2f bps, %s)\n", mode_string, 100.0 - ratio * 100.0, bps, md5_string2);
return 0;
}
// Thread / function that opens a virtual WavPack file, decodes it and calculates the MD5 hash of the
// decoded audio data.
#define DECODE_SAMPLES 200000 // long enough for temporal multithreading
#ifdef _WIN32
static unsigned WINAPI decode_thread (LPVOID threadid)
#else
static void *decode_thread (void *threadid)
#endif
{
WavpackDecoder *wd = (WavpackDecoder *) threadid;
char error [80];
WavpackContext *wpc;
int32_t *decoded_samples, num_chans, bps;
MD5_CTX md5_context;
int open_flags = 0;
if (worker_threads)
open_flags |= worker_threads << OPEN_THREADS_SHFT;
wpc = WavpackOpenFileInputEx (&freader, wd->wv_stream, wd->wvc_stream, error, open_flags, 0);
if (!wpc) {
printf ("decode_thread(): error \"%s\" opening input file\n", error);
wd->num_errors = 1;
wp_thread_exit (0);
}
MD5_Init (&md5_context);
num_chans = WavpackGetNumChannels (wpc);
bps = WavpackGetBytesPerSample (wpc);
decoded_samples = malloc (sizeof (int32_t) * DECODE_SAMPLES * num_chans);
if (!decoded_samples) {
printf ("decode_thread(): can't allocate memory!\n");
exit (-1);
}
while (1) {
int samples = WavpackUnpackSamples (wpc, decoded_samples, DECODE_SAMPLES);
if (!samples)
break;
store_samples (decoded_samples, decoded_samples, 0, bps, samples * num_chans);
MD5_Update (&md5_context, (unsigned char *) decoded_samples, bps * samples * num_chans);
wd->sample_count += samples;
}
MD5_Final (wd->md5_decoded, &md5_context);
wd->num_errors = WavpackGetNumErrors (wpc);
free (decoded_samples);
WavpackCloseFile (wpc);
wp_thread_exit (0);
return 0;
}
// This code implements a simple virtual "file" so that we can have a WavPack encoding process and
// a WavPack decoding process running at the same time (using Pthreads).
static int write_block (void *id, void *data, int32_t length)
{
StreamingFile *ws = (StreamingFile *) id;
unsigned char *data_ptr = data;
if (!ws || !data || !length)
return 0;
// if (frandom() < .0001)
// ((char *) data) [(int) floor (length * frandom())] ^= 1;
if (!ws->first_block_size)
ws->first_block_size = length;
ws->bytes_written += length;
if (ws->file && !ws->error) {
if (!fwrite (data, 1, length, ws->file)) {
ws->error = 1;
fclose (ws->file);
ws->file = NULL;
}
}
if (!ws->buffer_size) // if no buffer, just swallow data silently
return 1;
wp_mutex_obtain (ws->mutex);
while (length) {
int32_t bytes_available = (int32_t) (ws->buffer_tail - ws->buffer_head - 1);
int32_t bytes_to_copy = length;
if (bytes_available < 0)
bytes_available += ws->buffer_size;
if (bytes_available < bytes_to_copy)
bytes_to_copy = bytes_available;
if (ws->buffer_head + bytes_to_copy > ws->buffer_base + ws->buffer_size)
bytes_to_copy = (int32_t) (ws->buffer_base + ws->buffer_size - ws->buffer_head);
if (!bytes_to_copy) {
ws->full_waits++;
wp_condvar_wait (ws->cond_read, ws->mutex);
continue;
}
memcpy ((void *) ws->buffer_head, data_ptr, bytes_to_copy);
if ((ws->buffer_head += bytes_to_copy) == ws->buffer_base + ws->buffer_size)
ws->buffer_head = ws->buffer_base;
data_ptr += bytes_to_copy;
length -= bytes_to_copy;
wp_condvar_signal (ws->cond_write);
}
wp_mutex_release (ws->mutex);
return 1;
}
static int32_t read_bytes (void *id, void *data, int32_t bcount)
{
StreamingFile *ws = (StreamingFile *) id;
unsigned char *data_ptr = data;
wp_mutex_obtain (ws->mutex);
while (bcount) {
if (ws->push_back) {
*data_ptr++ = ws->push_back;
ws->push_back = 0;
bcount--;
wp_condvar_signal (ws->cond_read);
}
else if (ws->buffer_head != ws->buffer_tail) {
int bytes_available = (int) (ws->buffer_head - ws->buffer_tail);
int32_t bytes_to_copy = bcount;
if (bytes_available < 0)
bytes_available += ws->buffer_size;
if (bytes_available < bytes_to_copy)
bytes_to_copy = bytes_available;
if (ws->buffer_tail + bytes_to_copy > ws->buffer_base + ws->buffer_size)
bytes_to_copy = (int32_t) (ws->buffer_base + ws->buffer_size - ws->buffer_tail);
memcpy (data_ptr, (void *) ws->buffer_tail, bytes_to_copy);
if ((ws->buffer_tail += bytes_to_copy) == ws->buffer_base + ws->buffer_size)
ws->buffer_tail = ws->buffer_base;
ws->bytes_read += bytes_to_copy;
data_ptr += bytes_to_copy;
bcount -= bytes_to_copy;
wp_condvar_signal (ws->cond_read);
}
else if (ws->done)
break;
else {
ws->empty_waits++;
wp_condvar_wait (ws->cond_write, ws->mutex);
}
}
wp_mutex_release (ws->mutex);
return (int32_t) (data_ptr - (unsigned char *) data);
}
static uint32_t get_pos (void *id)
{
return -1;
}
static int set_pos_abs (void *id, uint32_t pos)
{
return 0;
}
static int set_pos_rel (void *id, int32_t delta, int mode)
{
return -1;
}
static int push_back_byte (void *id, int c)
{
StreamingFile *ws = (StreamingFile *) id;
if (!ws->push_back)
return ws->push_back = c;
else
return EOF;
}
static uint32_t get_length (void *id)
{
return 0;
}
static int can_seek (void *id)
{
return 0;
}
static WavpackStreamReader freader = {
read_bytes, get_pos, set_pos_abs, set_pos_rel, push_back_byte, get_length, can_seek,
};
static void initialize_stream (StreamingFile *ws, int buffer_size)
{
if (buffer_size) {
ws->buffer_base = malloc (ws->buffer_size = buffer_size);
ws->buffer_head = ws->buffer_tail = ws->buffer_base;
wp_condvar_init (ws->cond_write);
wp_condvar_init (ws->cond_read);
wp_mutex_init (ws->mutex);
}
}
static void flush_stream (StreamingFile *ws)
{
if (ws->buffer_base) {
wp_mutex_obtain (ws->mutex);
ws->done = 1;
wp_condvar_signal (ws->cond_write);
wp_mutex_release (ws->mutex);
}
}
static void free_stream (StreamingFile *ws)
{
if (ws->file) {
fclose (ws->file);
ws->file = NULL;
}
if (ws->buffer_base) {
free ((void *) ws->buffer_base);
ws->buffer_base = NULL;
}
}
// Helper utilities for generating the audio used for testing.
// Return a random value in the range: 0.0 <= n < 1.0
static double frandom (void)
{
static uint64_t random = 0x3141592653589793ULL;
random = ((random << 4) - random) ^ 1;
random = ((random << 4) - random) ^ 1;
random = ((random << 4) - random) ^ 1;
return (random >> 32) / 4294967296.0;
}
static void tone_generator_init (struct audio_generator *cxt, int sample_rate, int low_freq, int high_freq)
{
struct tone_generator *tone_cxt = &cxt->u.tone_cxt;
memset (cxt, 0, sizeof (*cxt));
cxt->type = tone;
tone_cxt->sample_rate = sample_rate;
tone_cxt->high_frequency = high_freq;
tone_cxt->low_frequency = low_freq;
tone_cxt->samples_per_update = sample_rate / low_freq * 4;
}
static void tone_generator_run (struct tone_generator *cxt, float *samples, int num_samples)
{
double target_frequency, target_velocity;
while (num_samples--) {
if (!cxt->samples_left) {
cxt->samples_left = cxt->samples_per_update;
target_frequency = cxt->low_frequency * pow (cxt->high_frequency / cxt->low_frequency, frandom ());
target_velocity = (M_PI * 2.0) / ((float) cxt->sample_rate / target_frequency);
cxt->acceleration = (target_velocity - cxt->velocity) / cxt->samples_left;
}
*samples++ = (float) sin (cxt->angle += cxt->velocity += cxt->acceleration);
if (cxt->angle > M_PI) cxt->angle -= M_PI * 2.0;
cxt->samples_left--;
}
}
static void noise_generator_init (struct audio_generator *cxt, double factor)
{
struct noise_generator *noise_cxt = &cxt->u.noise_cxt;
memset (cxt, 0, sizeof (*cxt));
cxt->type = noise;
noise_cxt->scalar = factor * factor * factor * sqrt (factor) / (2.0 + factor * factor);
noise_cxt->factor = factor;
}
static void noise_generator_run (struct noise_generator *cxt, float *samples, int num_samples)
{
while (num_samples--) {
double source = (frandom () - 0.5) * cxt->scalar;
cxt->sum1 += (source - cxt->sum1) / cxt->factor;
cxt->sum2 += (cxt->sum1 - cxt->sum2) / cxt->factor;
*samples++ = (float) (cxt->sum2 - cxt->sum2p);
cxt->sum2p = cxt->sum2;
}
}
static void audio_generator_run (struct audio_generator *cxt, float *samples, int num_samples)
{
switch (cxt->type) {
case noise:
noise_generator_run (&cxt->u.noise_cxt, samples, num_samples);
break;
case tone:
tone_generator_run (&cxt->u.tone_cxt, samples, num_samples);
break;
default:
printf ("bad audio generator type!\n");
exit (-1);
}
}
static void mix_samples_with_gain (float *destin, float *source, int num_samples, int num_chans, double initial_gain, double final_gain)
{
double delta_gain = (final_gain - initial_gain) / num_samples;
double gain = initial_gain - delta_gain;
while (num_samples--) {
*destin += *source++ * (float) (gain += delta_gain);
destin += num_chans;
}
}
static void truncate_float_samples (float *samples, int num_samples, int bits)
{
int isample, imin = -(1 << (bits - 1)), imax = (1 << (bits - 1)) - 1;
double scalar = (double) (1 << (bits - 1));
while (num_samples--) {
if (*samples >= 1.0)
isample = imax;
else if (*samples <= -1.0)
isample = imin;
else
isample = (int) floor (*samples * scalar);
*samples++ = (float) (isample / scalar);
}
}
static void float_to_integer_samples (float *samples, int num_samples, int bits)
{
int isample, imin = -(1 << (bits - 1)), imax = (1 << (bits - 1)) - 1;
double scalar = (double) (1 << (bits - 1));
int ishift = (8 - (bits & 0x7)) & 0x7;
while (num_samples--) {
if (*samples >= 1.0)
isample = imax;
else if (*samples <= -1.0)
isample = imin;
else
isample = (int) floor (*samples * scalar);
*(int32_t *)samples = (uint32_t) isample << ishift;
samples++;
}
}
static void float_to_32bit_integer_samples (float *samples, int num_samples, int test_flags)
{
int isample, imin = 0x8000000, imax = 0x7fffffff;
double scalar = 2147483648.0;
while (num_samples--) {
if (*samples >= 1.0)
isample = imax;
else if (*samples <= -1.0)
isample = imin;
else
isample = (int) floor (*samples * scalar);
// if there are trailing zeros, fill them in with random data (if enabled)
if ((test_flags & TEST_FLAG_INT32_FILL_LOW_BITS) && isample && !(isample & 1)) {
int tzeros = 1;
while (!((isample >>= 1) & 1))
tzeros++;
while (tzeros--)
isample = ((unsigned int) isample << 1) + ((frandom() > 0.5) ? 1 : 0);
}
*(int32_t *)samples = isample;
samples++;
}
}
// Code to store samples. Source is an array of int32_t data (which is what WavPack uses
// internally), but the destination can have from 1 to 4 bytes per sample. Also, the destination
// data is assumed to be little-endian and signed, except for byte data which is unsigned (these
// are WAV file defaults). The endian and signedness can be overridden with the qmode flags
// to support other formats.
static void *store_little_endian_unsigned_samples (void *dst, int32_t *src, int bps, int count);
static void *store_little_endian_signed_samples (void *dst, int32_t *src, int bps, int count);
static void *store_big_endian_unsigned_samples (void *dst, int32_t *src, int bps, int count);
static void *store_big_endian_signed_samples (void *dst, int32_t *src, int bps, int count);
static void *store_samples (void *dst, int32_t *src, int qmode, int bps, int count)
{
if (qmode & QMODE_BIG_ENDIAN) {
if ((qmode & QMODE_UNSIGNED_WORDS) || (bps == 1 && !(qmode & QMODE_SIGNED_BYTES)))
return store_big_endian_unsigned_samples (dst, src, bps, count);
else
return store_big_endian_signed_samples (dst, src, bps, count);
}
else if ((qmode & QMODE_UNSIGNED_WORDS) || (bps == 1 && !(qmode & (QMODE_SIGNED_BYTES | QMODE_DSD_AUDIO))))
return store_little_endian_unsigned_samples (dst, src, bps, count);
else
return store_little_endian_signed_samples (dst, src, bps, count);
}
static void *store_little_endian_unsigned_samples (void *dst, int32_t *src, int bps, int count)
{
unsigned char *dptr = dst;
int32_t temp;
switch (bps) {
case 1:
while (count--)
*dptr++ = *src++ + 0x80;
break;
case 2:
while (count--) {
*dptr++ = (unsigned char) (temp = *src++ + 0x8000);
*dptr++ = (unsigned char) (temp >> 8);
}
break;
case 3:
while (count--) {
*dptr++ = (unsigned char) (temp = *src++ + 0x800000);
*dptr++ = (unsigned char) (temp >> 8);
*dptr++ = (unsigned char) (temp >> 16);
}
break;
case 4:
while (count--) {
*dptr++ = (unsigned char) (temp = *src++ + 0x80000000);
*dptr++ = (unsigned char) (temp >> 8);
*dptr++ = (unsigned char) (temp >> 16);
*dptr++ = (unsigned char) (temp >> 24);
}
break;
}
return dptr;
}
static void *store_little_endian_signed_samples (void *dst, int32_t *src, int bps, int count)
{
unsigned char *dptr = dst;
int32_t temp;
switch (bps) {
case 1:
while (count--)
*dptr++ = *src++;
break;
case 2:
while (count--) {
*dptr++ = (unsigned char) (temp = *src++);
*dptr++ = (unsigned char) (temp >> 8);
}
break;
case 3:
while (count--) {
*dptr++ = (unsigned char) (temp = *src++);
*dptr++ = (unsigned char) (temp >> 8);
*dptr++ = (unsigned char) (temp >> 16);
}
break;
case 4:
while (count--) {
*dptr++ = (unsigned char) (temp = *src++);
*dptr++ = (unsigned char) (temp >> 8);
*dptr++ = (unsigned char) (temp >> 16);
*dptr++ = (unsigned char) (temp >> 24);
}
break;
}
return dptr;
}
static void *store_big_endian_unsigned_samples (void *dst, int32_t *src, int bps, int count)
{
unsigned char *dptr = dst;
int32_t temp;
switch (bps) {
case 1:
while (count--)
*dptr++ = *src++ + 0x80;
break;
case 2:
while (count--) {
*dptr++ = (unsigned char) ((temp = *src++ + 0x8000) >> 8);
*dptr++ = (unsigned char) temp;
}
break;
case 3:
while (count--) {
*dptr++ = (unsigned char) ((temp = *src++ + 0x800000) >> 16);
*dptr++ = (unsigned char) (temp >> 8);
*dptr++ = (unsigned char) temp;
}
break;
case 4:
while (count--) {
*dptr++ = (unsigned char) ((temp = *src++ + 0x80000000) >> 24);
*dptr++ = (unsigned char) (temp >> 16);
*dptr++ = (unsigned char) (temp >> 8);
*dptr++ = (unsigned char) temp;
}
break;
}
return dptr;
}
static void *store_big_endian_signed_samples (void *dst, int32_t *src, int bps, int count)
{
unsigned char *dptr = dst;
int32_t temp;
switch (bps) {
case 1:
while (count--)
*dptr++ = *src++;
break;
case 2:
while (count--) {
*dptr++ = (unsigned char) ((temp = *src++) >> 8);
*dptr++ = (unsigned char) temp;
}
break;
case 3:
while (count--) {
*dptr++ = (unsigned char) ((temp = *src++) >> 16);
*dptr++ = (unsigned char) (temp >> 8);
*dptr++ = (unsigned char) temp;
}
break;
case 4:
while (count--) {
*dptr++ = (unsigned char) ((temp = *src++) >> 24);
*dptr++ = (unsigned char) (temp >> 16);
*dptr++ = (unsigned char) (temp >> 8);
*dptr++ = (unsigned char) temp;
}
break;
}
return dptr;
}
Computing file changes ...