1 ////////////////////////////////////////////////////////////////////////////
2 //                           **** WAVPACK ****                            //
3 //                  Hybrid Lossless Wavefile Compressor                   //
4 //                Copyright (c) 1998 - 2020 David Bryant.                 //
5 //                          All Rights Reserved.                          //
6 //      Distributed under the BSD Software License (see license.txt)      //
7 ////////////////////////////////////////////////////////////////////////////
8 
9 // wvtest.c
10 
11 // This is the main module for the WavPack command-line library tester.
12 
13 #include <stdlib.h>
14 #include <string.h>
15 #include <stdio.h>
16 #include <ctype.h>
17 #include <math.h>
18 #include <pthread.h>
19 
20 #include "wavpack.h"
21 #include "utils.h"                  // for PACKAGE_VERSION, etc.
22 #include "md5.h"
23 
24 #define CLEAR(destin) memset (&destin, 0, sizeof (destin));
25 
26 #ifndef M_PI
27 #define M_PI 3.14159265358979323
28 #endif
29 
30 static const char *sign_on = "\n"
31 " WVTEST  libwavpack Tester/Exerciser for WavPack  %s Version %s\n"
32 " Copyright (c) 2020 David Bryant.  All Rights Reserved.\n\n";
33 
34 static const char *version_warning = "\n"
35 " WARNING: WVTEST using libwavpack version %s, expected %s (see README)\n\n";
36 
37 static const char *usage =
38 " Usage:   WVTEST --default|--exhaustive [-options]\n"
39 "          WVTEST --seektest[=n] file.wv [...] (n=runs per file, def=1)\n\n"
40 " Options: --default           = perform the default test suite\n"
41 "          --exhaustive        = perform the exhaustive test suite\n"
42 "          --short             = perform shorter runs of each test\n"
43 "          --long              = perform longer runs of each test\n"
44 "          --no-decode         = skip the decoding process\n"
45 "          --no-extras         = skip the \"extra\" modes\n"
46 "          --no-hybrid         = skip the hybrid modes\n"
47 "          --no-floats         = skip the float modes\n"
48 "          --no-lossy          = skip the lossy modes\n"
49 "          --no-speeds         = skip the speed modes (fast, high, etc.)\n"
50 "          --help              = display this message\n"
51 "          --version           = write the version to stdout\n"
52 "          --write=n[-n][,...] = write specific test(s) (or range(s)) to disk\n\n"
53 " Web:     Visit www.wavpack.com for latest version and info\n";
54 
55 #define TEST_FLAG_EXTRA_MODE(x) ((x) & TEST_FLAG_EXTRA_MASK)
56 #define TEST_FLAG_EXTRA_MASK            0x7
57 #define TEST_FLAG_FLOAT_DATA            0x8
58 #define TEST_FLAG_WRITE_FILE            0x10
59 #define TEST_FLAG_DEFAULT               0x20
60 #define TEST_FLAG_EXHAUSTIVE            0x40
61 #define TEST_FLAG_NO_FLOATS             0x80
62 #define TEST_FLAG_NO_HYBRID             0x100
63 #define TEST_FLAG_NO_EXTRAS             0x200
64 #define TEST_FLAG_NO_LOSSY              0x400
65 #define TEST_FLAG_NO_SPEEDS             0x800
66 #define TEST_FLAG_STORE_FLOAT_AS_INT32  0x1000
67 #define TEST_FLAG_STORE_INT32_AS_FLOAT  0x2000
68 #define TEST_FLAG_IGNORE_WVC            0x4000
69 #define TEST_FLAG_NO_DECODE             0x8000
70 
71 static int run_test_size_modes (int wpconfig_flags, int test_flags, int base_minutes);
72 static int run_test_speed_modes (int wpconfig_flags, int test_flags, int bits, int num_chans, int num_seconds);
73 static int run_test_extra_modes (int wpconfig_flags, int test_flags, int bits, int num_chans, int num_seconds);
74 static int run_test (int wpconfig_flags, int test_flags, int bits, int num_chans, int num_seconds);
75 
76 #define NUM_WRITE_RANGES 10
77 static struct { int start, stop; } write_ranges [NUM_WRITE_RANGES];
78 int number_of_ranges;
79 
80 enum generator_type { noise, tone };
81 
82 struct audio_generator {
83     enum generator_type type;
84     union {
85         struct noise_generator {
86             float sum1, sum2, sum2p;                // these are changing
87             float factor, scalar;                   // these are constant
88         } noise_cxt;
89 
90         struct tone_generator {
91             int sample_rate, samples_per_update;    // these are constant
92             int high_frequency, low_frequency;      // these are constant
93             float angle, velocity, acceleration;    // these are changing
94             int samples_left;
95         } tone_cxt;
96     } u;
97 };
98 
99 static int seeking_test (char *filename, uint32_t test_count);
100 static void tone_generator_init (struct audio_generator *cxt, int sample_rate, int low_freq, int high_freq);
101 static void noise_generator_init (struct audio_generator *cxt, float factor);
102 static void audio_generator_run (struct audio_generator *cxt, float *samples, int num_samples);
103 static void mix_samples_with_gain (float *destin, float *source, int num_samples, int num_chans, float initial_gain, float final_gain);
104 static void truncate_float_samples (float *samples, int num_samples, int bits);
105 static void float_to_integer_samples (float *samples, int num_samples, int bits);
106 static void float_to_32bit_integer_samples (float *samples, int num_samples);
107 static void *store_samples (void *dst, int32_t *src, int qmode, int bps, int count);
108 static double frandom (void);
109 
110 typedef struct {
111     uint32_t buffer_size, bytes_written, bytes_read, first_block_size;
112     volatile unsigned char *buffer_base, *buffer_head, *buffer_tail;
113     int push_back, done, error, empty_waits, full_waits;
114     pthread_cond_t cond_read, cond_write;
115     pthread_mutex_t mutex;
116     FILE *file;
117 } StreamingFile;
118 
119 typedef struct {
120     StreamingFile *wv_stream, *wvc_stream;
121     unsigned char md5_decoded [16];
122     uint32_t sample_count;
123     int num_errors;
124 } WavpackDecoder;
125 
126 static void initialize_stream (StreamingFile *ws, int buffer_size);
127 static int write_block (void *id, void *data, int32_t length);
128 static void flush_stream (StreamingFile *ws);
129 static void free_stream (StreamingFile *ws);
130 static void *decode_thread (void *threadid);
131 static WavpackStreamReader freader;
132 
133 //////////////////////////////////////// main () function for CLI //////////////////////////////////////
134 
main(argc,argv)135 int main (argc, argv) int argc; char **argv;
136 {
137     int wpconfig_flags = CONFIG_MD5_CHECKSUM | CONFIG_OPTIMIZE_MONO, test_flags = 0, base_minutes = 2, res;
138     int seektest = 0;
139 
140     // loop through command-line arguments
141 
142     while (--argc) {
143         if (**++argv == '-' && (*argv)[1] == '-' && (*argv)[2]) {
144             char *long_option = *argv + 2, *long_param = long_option;
145 
146             while (*long_param)
147                 if (*long_param++ == '=')
148                     break;
149 
150             if (!strcmp (long_option, "help")) {                        // --help
151                 printf ("%s", usage);
152                 return 0;
153             }
154             else if (!strcmp (long_option, "version")) {                // --version
155                 printf ("wvtest %s\n", PACKAGE_VERSION);
156                 printf ("libwavpack %s\n", WavpackGetLibraryVersionString ());
157                 return 0;
158             }
159             else if (!strcmp (long_option, "short")) {                  // --short
160                 base_minutes = 1;
161             }
162             else if (!strcmp (long_option, "long")) {                   // --long
163                 base_minutes = 5;
164             }
165             else if (!strcmp (long_option, "default")) {                // --default
166                 test_flags |= TEST_FLAG_DEFAULT;
167             }
168             else if (!strcmp (long_option, "exhaustive")) {             // --exhaustive
169                 test_flags |= TEST_FLAG_EXHAUSTIVE;
170             }
171             else if (!strcmp (long_option, "no-extras")) {              // --no-extras
172                 test_flags |= TEST_FLAG_NO_EXTRAS;
173             }
174             else if (!strcmp (long_option, "no-hybrid")) {              // --no-hybrid
175                 test_flags |= TEST_FLAG_NO_HYBRID;
176             }
177             else if (!strcmp (long_option, "no-lossy")) {               // --no-lossy
178                 test_flags |= TEST_FLAG_NO_LOSSY;
179             }
180             else if (!strcmp (long_option, "no-speeds")) {              // --no-speeds
181                 test_flags |= TEST_FLAG_NO_SPEEDS;
182             }
183             else if (!strcmp (long_option, "no-floats")) {              // --no-floats
184                 test_flags |= TEST_FLAG_NO_FLOATS;
185             }
186             else if (!strcmp (long_option, "no-decode")) {              // --no-decode
187                 test_flags |= TEST_FLAG_NO_DECODE;
188             }
189             else if (!strncmp (long_option, "write", 5)) {              // --write
190                 for (number_of_ranges = 0; *long_param && isdigit (*long_param) && number_of_ranges < NUM_WRITE_RANGES;) {
191                     write_ranges [number_of_ranges].start = strtol (long_param, &long_param, 10);
192 
193                     if (*long_param == '-') {
194                         long_param++;
195                         if (isdigit (*long_param))
196                             write_ranges [number_of_ranges].stop = strtol (long_param, &long_param, 10);
197                         else
198                             break;
199                     }
200                     else
201                         write_ranges [number_of_ranges].stop = write_ranges [number_of_ranges].start;
202 
203                     number_of_ranges++;
204 
205                     if (*long_param == ',')
206                         long_param++;
207                     else
208                         break;
209                 }
210 
211                 if (*long_param || !number_of_ranges) {
212                     printf ("syntax error in write specification!\n");
213                     return 1;
214                 }
215                 else
216                     test_flags |= TEST_FLAG_WRITE_FILE;
217             }
218             else if (!strncmp (long_option, "seektest", 8)) {           // --seektest[=n]
219                 if (*long_param)
220                     seektest = strtol (long_param, NULL, 10);
221                 else
222                     seektest = 1;
223 
224                 if (seektest)
225                     break;
226             }
227             else {
228                 printf ("unknown option: %s !\n", long_option);
229                 return 1;
230             }
231         }
232         else {
233             printf ("unknown option: %s !\n", *argv);
234             return 1;
235         }
236     }
237 
238     if (strcmp (WavpackGetLibraryVersionString (), PACKAGE_VERSION))
239         printf (version_warning, WavpackGetLibraryVersionString (), PACKAGE_VERSION);
240     else
241         printf (sign_on, VERSION_OS, WavpackGetLibraryVersionString ());
242 
243     if (!seektest && !(test_flags & (TEST_FLAG_DEFAULT | TEST_FLAG_EXHAUSTIVE))) {
244         puts (usage);
245         return 1;
246     }
247 
248     if (seektest) {
249         while (--argc)
250             if ((res = seeking_test (*++argv, seektest)))
251                 break;
252     }
253     else {
254         printf ("\n\n                          ****** pure lossless ******\n");
255         res = run_test_size_modes (wpconfig_flags, test_flags, base_minutes);
256         if (res) goto done;
257 
258         if (!(test_flags & TEST_FLAG_NO_HYBRID)) {
259             printf ("\n\n                         ****** hybrid lossless ******\n");
260             res = run_test_size_modes (wpconfig_flags | CONFIG_HYBRID_FLAG | CONFIG_CREATE_WVC, test_flags, base_minutes);
261             if (res) goto done;
262 
263             if (!(test_flags & TEST_FLAG_NO_LOSSY)) {
264                 printf ("\n\n                          ****** hybrid lossy ******\n");
265                 res = run_test_size_modes (wpconfig_flags | CONFIG_HYBRID_FLAG, test_flags, base_minutes);
266                 if (res) goto done;
267 
268                 printf ("\n\n            ****** hybrid lossless (but ignore wvc on decode) ******\n");
269                 res = run_test_size_modes (wpconfig_flags | CONFIG_HYBRID_FLAG | CONFIG_CREATE_WVC,
270                     test_flags | TEST_FLAG_IGNORE_WVC, base_minutes);
271                 if (res) goto done;
272             }
273         }
274     }
275 
276 done:
277     if (res)
278         printf ("\ntest failed!\n\n");
279     else
280         printf ("\nall tests pass\n\n");
281 
282     return res;
283 }
284 
285 // Function to stress-test the WavpackSeekSample() API. Given the specified WavPack file, perform
286 // the specified number of seektest runs on that file. For each test run, a different, random
287 // seek interval is chosen. Note that MD5 sums are calculated for each chunk interval so we
288 // actually verify that every sample decoded is correct. For each test run, we decode the entire
289 // file 4 times over, on average.
290 
seeking_test(char * filename,uint32_t test_count)291 static int seeking_test (char *filename, uint32_t test_count)
292 {
293     char error [80];
294     WavpackContext *wpc = WavpackOpenFileInput (filename, error, OPEN_WVC | OPEN_DSD_NATIVE | OPEN_ALT_TYPES, 0);
295     int64_t min_chunk_size = 256, total_samples, sample_count = 0;
296     char md5_string1 [] = "????????????????????????????????";
297     char md5_string2 [] = "????????????????????????????????";
298     int32_t *decoded_samples, num_chans, bps, test_index, qmode;
299     unsigned char md5_initial [16], md5_stored [16];
300     MD5_CTX md5_global, md5_local;
301     unsigned char *chunked_md5;
302 
303     printf ("\n-------------------- file: %s %s--------------------\n",
304         filename, (WavpackGetMode (wpc) & MODE_WVC) ? "(+wvc) " : "");
305 
306     if (!wpc) {
307         printf ("seeking_test(): error \"%s\" opening input file \"%s\"\n", error, filename);
308         return -1;
309     }
310 
311     num_chans = WavpackGetNumChannels (wpc);
312     total_samples = WavpackGetNumSamples64 (wpc);
313     bps = WavpackGetBytesPerSample (wpc);
314     qmode = WavpackGetQualifyMode (wpc);
315 
316     if (total_samples < 2 || total_samples == -1) {
317         printf ("seeking_test(): can't determine file size!\n");
318         return -1;
319     }
320 
321     if (qmode & QMODE_DSD_IN_BLOCKS) {
322         printf ("seeking_test(): can't handle blocked DSD audio (i.e., from .dsf files)!\n");
323         return -1;
324     }
325 
326     // For very short files, reduce the minimum chunk size
327 
328     while (min_chunk_size > 1 && total_samples / min_chunk_size < 256)
329         min_chunk_size /= 2;
330 
331     for (test_index = 0; test_index < test_count; test_index++) {
332         uint32_t chunk_samples, total_chunks, chunk_count = 0, seek_count = 0;
333 
334         chunk_samples = min_chunk_size + frandom () * min_chunk_size;   // 256 - 511 (unless reduced)
335         total_chunks = (total_samples + chunk_samples - 1) / chunk_samples;
336         decoded_samples = malloc (sizeof (int32_t) * chunk_samples * num_chans);
337         chunked_md5 = malloc (total_chunks * 16);
338 
339         if (!chunked_md5 || !decoded_samples) {
340             printf ("seeking_test(): can't allocate memory!\n");
341             return -1;
342         }
343 
344         sample_count = chunk_count = 0;
345         MD5_Init (&md5_global);
346 
347         // read the entire file, calculating the MD5 sums for the whole file and for each "chunk"
348 
349         while (1) {
350             int samples = WavpackUnpackSamples (wpc, decoded_samples, chunk_samples);
351 
352             if (!samples)
353                 break;
354 
355             store_samples (decoded_samples, decoded_samples, qmode, bps, samples * num_chans);
356             MD5_Update (&md5_global, (unsigned char *) decoded_samples, bps * samples * num_chans);
357 
358             MD5_Init (&md5_local);
359             MD5_Update (&md5_local, (unsigned char *) decoded_samples, bps * samples * num_chans);
360             MD5_Final (chunked_md5 + chunk_count * 16, &md5_local);
361 
362             sample_count += samples;
363             chunk_count++;
364         }
365 
366         if (WavpackGetNumErrors (wpc)) {
367             printf ("seeking_test(): decoder reported %d errors!\n", WavpackGetNumErrors (wpc));
368             return -1;
369         }
370 
371         if (total_samples != sample_count) {
372             printf ("seeking_test(): sample count is not correct!\n");
373             return -1;
374         }
375 
376         if (total_chunks != chunk_count) {
377             printf ("seeking_test(): chunk count is not correct (not sure if this can happen)!\n");
378             return -1;
379         }
380 
381         // The first time through the file we verify that the MD5 sum matches what's stored in the file
382         // (if one is stored there). On subsequent tests, we verify that the whole-file MD5 sum matches
383         // what we got the first time.
384 
385         if (!test_index) {
386             int file_has_md5 = WavpackGetMD5Sum (wpc, md5_stored), i;
387 
388             MD5_Final (md5_initial, &md5_global);
389 
390             for (i = 0; i < 16; ++i) {
391                 sprintf (md5_string1 + (i * 2), "%02x", md5_stored [i]);
392                 sprintf (md5_string2 + (i * 2), "%02x", md5_initial [i]);
393             }
394 
395             printf ("stored/actual sample count: %lld / %lld\n", (long long int) total_samples, (long long int) sample_count);
396             if (file_has_md5) printf ("stored md5: %s\n", md5_string1);
397             printf ("actual md5: %s\n", md5_string2);
398 
399             if (WavpackGetMode (wpc) & MODE_LOSSLESS)
400                 if (file_has_md5 && memcmp (md5_stored, md5_initial, sizeof (md5_stored))) {
401                     printf ("seeking_test(): MD5 does not match MD5 stored in file!\n");
402                     return -1;
403                 }
404         }
405         else {
406             unsigned char md5_subsequent [16];
407 
408             MD5_Final (md5_subsequent, &md5_global);
409 
410             if (memcmp (md5_subsequent, md5_initial, sizeof (md5_stored))) {
411                 printf ("seeking_test(): MD5 does not match MD5 read initially!\n");
412                 return -1;
413             }
414         }
415 
416         // Half the time, reopen the file. This lets us catch errors caused by seeking to locations
417         // that have never been decoded (at least not for this open call).
418 
419         if (frandom() < 0.5) {
420             WavpackCloseFile (wpc);
421             wpc = WavpackOpenFileInput (filename, error, OPEN_WVC | OPEN_DSD_NATIVE | OPEN_ALT_TYPES, 0);
422 
423             if (!wpc) {
424                 printf ("seeking_test(): error \"%s\" reopening input file \"%s\"\n", error, filename);
425                 return -1;
426             }
427         }
428 
429         chunk_count *= 4;       // decode each chunk 4 times, on average
430 
431         while (chunk_count) {
432             int start_chunk = 0, stop_chunk, current_chunk, num_chunks = 1;
433 
434             start_chunk = floor (frandom () * total_chunks);
435             if (start_chunk == total_chunks) start_chunk--;
436 
437             // At a minimum, we very one chunk after the seek. However, we also random chose additional
438             // chunks to verify so that sometimes we verify lots of data after the seek.
439 
440             while (start_chunk + num_chunks < total_chunks && frandom () < 0.667)
441                 num_chunks *= 2;
442 
443             if (start_chunk + num_chunks > total_chunks)
444                 num_chunks = total_chunks - start_chunk;
445 
446             stop_chunk = start_chunk + num_chunks - 1;
447 
448             if (!WavpackSeekSample64 (wpc, (int64_t) start_chunk * chunk_samples)) {
449                 printf ("seeking_test(): seek error!\n");
450                 return -1;
451             }
452 
453             for (current_chunk = start_chunk; current_chunk <= stop_chunk; ++current_chunk) {
454                 int samples = WavpackUnpackSamples (wpc, decoded_samples, chunk_samples);
455                 unsigned char md5_chunk [16];
456 
457                 if (!samples) {
458                     printf ("seeking_test(): seek error!\n");
459                     return -1;
460                 }
461 
462                 store_samples (decoded_samples, decoded_samples, qmode, bps, samples * num_chans);
463 
464                 // if (frandom() < 0.0001)
465                 //     decoded_samples [(int) floor (samples * frandom())] ^= 1;
466 
467                 MD5_Init (&md5_local);
468                 MD5_Update (&md5_local, (unsigned char *) decoded_samples, bps * samples * num_chans);
469                 MD5_Final (md5_chunk, &md5_local);
470 
471                 if (memcmp (chunked_md5 + current_chunk * 16, md5_chunk, sizeof (md5_chunk))) {
472                     printf ("seeking_test(): seek+decode error at %lld!\n", (long long int) current_chunk * chunk_samples);
473                     return -1;
474                 }
475 
476                 if (chunk_count)
477                     chunk_count--;
478                 else
479                     break;
480             }
481 
482             // display the "dot" every 10 seeks
483 
484             if (++seek_count % 10 == 0) {
485                 if (seek_count % 640) {
486                     putchar ('.'); fflush (stdout);
487                 }
488                 else
489                     puts (".");
490             }
491         }
492 
493         printf ("\nresult: %u successful seeks on %u-sample boundaries\n", seek_count, chunk_samples);
494 
495         if (!WavpackSeekSample (wpc, 0)) {
496             printf ("seeking_test(): rewind error!\n");
497             return -1;
498         }
499 
500         free (chunked_md5);
501         free (decoded_samples);
502     }
503 
504     WavpackCloseFile (wpc);
505     return 0;
506 }
507 
508 // Given a WavPack configuration and test flags, run the various combinations of
509 // bit-depth and channel configurations. A return value of FALSE indicates an error.
510 
run_test_size_modes(int wpconfig_flags,int test_flags,int base_minutes)511 static int run_test_size_modes (int wpconfig_flags, int test_flags, int base_minutes)
512 {
513     int res;
514 
515     printf ("\n   *** 8-bit, mono ***\n");
516     res = run_test_speed_modes (wpconfig_flags, test_flags, 8, 1, base_minutes*5*60);
517     if (res) return res;
518 
519     if (test_flags & TEST_FLAG_EXHAUSTIVE) {
520         printf ("\n   *** 16-bit, mono ***\n");
521         res = run_test_speed_modes (wpconfig_flags, test_flags, 16, 1, base_minutes*5*60);
522         if (res) return res;
523     }
524 
525     printf ("\n   *** 16-bit, stereo ***\n");
526     res = run_test_speed_modes (wpconfig_flags, test_flags, 16, 2, base_minutes*3*60);
527     if (res) return res;
528 
529     if ((test_flags & TEST_FLAG_EXHAUSTIVE) && !(test_flags & TEST_FLAG_NO_FLOATS)) {
530         printf ("\n   *** 16-bit (converted to float), stereo ***\n");
531         res = run_test_speed_modes (wpconfig_flags, test_flags | TEST_FLAG_FLOAT_DATA, 16, 2, base_minutes*3*60);
532         if (res) return res;
533     }
534 
535     printf ("\n   *** 24-bit, 5.1 channels ***\n");
536     res = run_test_speed_modes (wpconfig_flags, test_flags, 24, 6, base_minutes*60);
537     if (res) return res;
538 
539     if (test_flags & TEST_FLAG_EXHAUSTIVE) {
540         if (!(test_flags & TEST_FLAG_NO_FLOATS)) {
541             printf ("\n   *** 24-bit (converted to float), 5.1 channels ***\n");
542             res = run_test_speed_modes (wpconfig_flags, test_flags | TEST_FLAG_FLOAT_DATA, 24, 6, base_minutes*60);
543             if (res) return res;
544         }
545 
546         printf ("\n   *** 32-bit integer, 5.1 channels ***\n");
547         res = run_test_speed_modes (wpconfig_flags, test_flags, 32, 6, base_minutes*60);
548         if (res) return res;
549 
550         if (!(test_flags & TEST_FLAG_NO_FLOATS)) {
551             printf ("\n   *** 32-bit float stored as integer (pathological), 5.1 channels ***\n");
552             res = run_test_speed_modes (wpconfig_flags, test_flags | TEST_FLAG_STORE_FLOAT_AS_INT32, 32, 6, base_minutes*60);
553             if (res) return res;
554 
555             if (!(wpconfig_flags & CONFIG_HYBRID_FLAG)) {
556                 printf ("\n   *** 32-bit integer stored as float (pathological), 5.1 channels ***\n");
557                 res = run_test_speed_modes (wpconfig_flags, test_flags | TEST_FLAG_STORE_INT32_AS_FLOAT, 32, 6, base_minutes*60);
558                 if (res) return res;
559             }
560         }
561     }
562 
563     if (!(test_flags & TEST_FLAG_NO_FLOATS)) {
564         printf ("\n   *** 32-bit float, 5.1 channels ***\n");
565         res = run_test_speed_modes (wpconfig_flags, test_flags | TEST_FLAG_FLOAT_DATA, 32, 6, base_minutes*60);
566         if (res) return res;
567     }
568 
569     return 0;
570 }
571 
572 // Given a WavPack configuration and test flags, run the various combinations of
573 // speed modes (i.e, fast, high, etc). A return value of FALSE indicates an error.
574 
run_test_speed_modes(int wpconfig_flags,int test_flags,int bits,int num_chans,int num_seconds)575 static int run_test_speed_modes (int wpconfig_flags, int test_flags, int bits, int num_chans, int num_seconds)
576 {
577     int res;
578 
579     if (!(test_flags & TEST_FLAG_NO_SPEEDS)) {
580         res = run_test_extra_modes (wpconfig_flags | CONFIG_FAST_FLAG, test_flags, bits, num_chans, num_seconds);
581         if (res) return res;
582     }
583 
584     res = run_test_extra_modes (wpconfig_flags, test_flags, bits, num_chans, num_seconds);
585     if (res) return res;
586 
587     if (!(test_flags & TEST_FLAG_NO_SPEEDS)) {
588         res = run_test_extra_modes (wpconfig_flags | CONFIG_HIGH_FLAG, test_flags, bits, num_chans, num_seconds);
589         if (res) return res;
590     }
591 
592     if (!(test_flags & TEST_FLAG_NO_SPEEDS)) {
593         res = run_test_extra_modes (wpconfig_flags | CONFIG_VERY_HIGH_FLAG, test_flags, bits, num_chans, num_seconds);
594         if (res) return res;
595     }
596 
597     return 0;
598 }
599 
600 // Given a WavPack configuration and test flags, run the various combinations of "extra" modes (0-6).
601 // Note that except for the base mode (no extra), the "default" and "exhaustive" configurations do
602 // different extra modes. Combining the "default" and "exhaustive" configurations does all the extra
603 // modes. A return value of FALSE indicates an error.
604 
run_test_extra_modes(int wpconfig_flags,int test_flags,int bits,int num_chans,int num_seconds)605 static int run_test_extra_modes (int wpconfig_flags, int test_flags, int bits, int num_chans, int num_seconds)
606 {
607     int res;
608 
609     res = run_test (wpconfig_flags, test_flags, bits, num_chans, num_seconds);
610     if (res) return res;
611 
612     if (test_flags & TEST_FLAG_NO_EXTRAS)
613         return 0;
614 
615     if (test_flags & TEST_FLAG_EXHAUSTIVE) {
616         res = run_test (wpconfig_flags, test_flags | TEST_FLAG_EXTRA_MODE (1), bits, num_chans, num_seconds);
617         if (res) return res;
618     }
619 
620     if (test_flags & TEST_FLAG_DEFAULT) {
621         res = run_test (wpconfig_flags, test_flags | TEST_FLAG_EXTRA_MODE (2), bits, num_chans, num_seconds);
622         if (res) return res;
623     }
624 
625     if (test_flags & TEST_FLAG_EXHAUSTIVE) {
626         res = run_test (wpconfig_flags, test_flags | TEST_FLAG_EXTRA_MODE (3), bits, num_chans, num_seconds);
627         if (res) return res;
628     }
629 
630     if (test_flags & TEST_FLAG_EXHAUSTIVE) {
631         res = run_test (wpconfig_flags, test_flags | TEST_FLAG_EXTRA_MODE (4), bits, num_chans, num_seconds);
632         if (res) return res;
633     }
634 
635     if (test_flags & TEST_FLAG_DEFAULT) {
636         res = run_test (wpconfig_flags, test_flags | TEST_FLAG_EXTRA_MODE (5), bits, num_chans, num_seconds);
637         if (res) return res;
638     }
639 
640     if (test_flags & TEST_FLAG_EXHAUSTIVE) {
641         res = run_test (wpconfig_flags, test_flags | TEST_FLAG_EXTRA_MODE (6), bits, num_chans, num_seconds);
642         if (res) return res;
643     }
644 
645     return 0;
646 }
647 
648 // Given a WavPack configuration and test flags, actually run the specified test. This entails
649 // generating the actual audio test data, creating the "virtual" WavPack file and writing to it,
650 // and spawning the thread that will read the "virtual" file and do the decoding (which is obviously
651 // required to verify the entire encode/decode chain). For lossless modes, and MD5 hash is used to
652 // verify the result, otherwise the decoder is trusted to detect and report errors (although the
653 // total number of samples is verified).
654 
655 #define BUFFER_SIZE 1000000
656 #define NUM_GENERATORS 6
657 
658 struct audio_channel {
659     float audio_gain_hist [NUM_GENERATORS], audio_gain [NUM_GENERATORS], angle_offset;
660     int lfe_flag;
661 };
662 
663 #define SAMPLE_RATE 44100
664 #define ENCODE_SAMPLES 128
665 #define NOISE_GAIN 0.6667
666 #define TONE_GAIN 0.3333
667 
run_test(int wpconfig_flags,int test_flags,int bits,int num_chans,int num_seconds)668 static int run_test (int wpconfig_flags, int test_flags, int bits, int num_chans, int num_seconds)
669 {
670     static int test_number;
671 
672     float sequencing_angle = 0.0, speed = 60.0, width = 200.0, *source, *destin, ratio, bps;
673     int lossless = !(wpconfig_flags & CONFIG_HYBRID_FLAG) || ((wpconfig_flags & CONFIG_CREATE_WVC) && !(test_flags & TEST_FLAG_IGNORE_WVC));
674     char md5_string1 [] = "????????????????????????????????";
675     char md5_string2 [] = "????????????????????????????????";
676     uint32_t total_encoded_bytes, total_encoded_samples;
677     struct audio_generator generators [NUM_GENERATORS];
678     int seconds = 0, samples = 0, wc = 0, chan_mask;
679     char *filename = NULL, mode_string [32] = "-";
680     struct audio_channel *channels;
681     pthread_t pthread;
682     WavpackContext *out_wpc;
683     WavpackConfig wpconfig;
684     StreamingFile wv_stream, wvc_stream;
685     WavpackDecoder wv_decoder;
686     unsigned char md5_encoded [16];
687     MD5_CTX md5_context;
688     void *term_value;
689     int i, j, k;
690 
691     if (wpconfig_flags & CONFIG_FAST_FLAG)
692         strcat (mode_string, "f");
693     else if (wpconfig_flags & CONFIG_HIGH_FLAG)
694         strcat (mode_string, "h");
695     else if (wpconfig_flags & CONFIG_VERY_HIGH_FLAG)
696         strcat (mode_string, "hh");
697 
698     printf ("test %04d...", ++test_number); fflush (stdout);
699     MD5_Init (&md5_context);
700 
701     noise_generator_init (&generators [0], 128.0);
702     tone_generator_init (&generators [1], SAMPLE_RATE, 20, 200);
703     noise_generator_init (&generators [2], 12.0);
704     tone_generator_init (&generators [3], SAMPLE_RATE, 200, 2000);
705     noise_generator_init (&generators [4], 1.75);
706     tone_generator_init (&generators [5], SAMPLE_RATE, 2000, 20000);
707 
708     CLEAR (wpconfig);
709     CLEAR (wv_decoder);
710     CLEAR (wv_stream);
711     CLEAR (wvc_stream);
712 
713     channels = malloc (num_chans * sizeof (*channels));
714     source = malloc (ENCODE_SAMPLES * sizeof (*source));
715     destin = malloc (ENCODE_SAMPLES * num_chans * sizeof (*destin));
716 
717     if (!channels || !source || !destin) {
718         printf ("run_test(): can't allocate memory!\n");
719         exit (-1);
720     }
721 
722     memset (channels, 0, num_chans * sizeof (*channels));
723 
724     switch (num_chans) {
725         case 1:
726             channels [0].angle_offset = 0.0;
727             chan_mask = 0x4;
728             break;
729 
730         case 2:
731             channels [0].angle_offset -= M_PI / 24.0;
732             channels [1].angle_offset += M_PI / 24.0;
733             chan_mask = 0x3;
734             break;
735 
736         case 4:
737             channels [0].angle_offset -= M_PI / 24.0;
738             channels [1].angle_offset += M_PI / 24.0;
739             channels [2].angle_offset -= 23.0 * M_PI / 24.0;
740             channels [3].angle_offset += 23.0 * M_PI / 24.0;
741             chan_mask = 0x33;
742             break;
743 
744         case 6:
745             channels [0].angle_offset -= M_PI / 24.0;
746             channels [1].angle_offset += M_PI / 24.0;
747             channels [3].lfe_flag = 1;
748             channels [4].angle_offset -= 23.0 * M_PI / 24.0;
749             channels [5].angle_offset += 23.0 * M_PI / 24.0;
750             chan_mask = 0x3F;
751             break;
752 
753         default:
754             printf ("invalid channel count = %d\n", num_chans);
755             exit (-1);
756     }
757 
758     if (!(test_flags & TEST_FLAG_NO_DECODE)) {
759         initialize_stream (&wv_stream, BUFFER_SIZE);
760         wv_decoder.wv_stream = &wv_stream;
761     }
762     else
763         initialize_stream (&wv_stream, 0);
764 
765     if (test_flags & TEST_FLAG_WRITE_FILE) {
766         int i;
767 
768         for (i = 0; i < number_of_ranges; ++i)
769             if (test_number >= write_ranges [i].start && test_number <= write_ranges [i].stop) {
770                 filename = malloc (32);
771 
772                 if (!filename) {
773                     printf ("run_test(): can't allocate memory!\n");
774                     exit (-1);
775                 }
776 
777                 sprintf (filename, "testfile-%04d.wv", test_number);
778 
779                 if (((wv_stream.file = fopen (filename, "w+b")) == NULL)) {
780                     printf ("can't create file %s!\n", filename);
781                     free_stream (&wv_stream);
782                     return 1;
783                 }
784 
785                 break;
786             }
787     }
788 
789     if (wpconfig_flags & CONFIG_CREATE_WVC) {
790         if (!(test_flags & (TEST_FLAG_IGNORE_WVC | TEST_FLAG_NO_DECODE))) {
791             initialize_stream (&wvc_stream, BUFFER_SIZE);
792             wv_decoder.wvc_stream = &wvc_stream;
793         }
794         else
795             initialize_stream (&wvc_stream, 0);
796 
797         if (filename) {
798             char *filename_c = malloc (strlen (filename) + 10);
799 
800             strcpy (filename_c, filename);
801             strcat (filename_c, "c");
802 
803             if ((wvc_stream.file = fopen (filename_c, "w+b")) == NULL) {
804                 printf ("can't create file %s!\n", filename_c);
805                 free_stream (&wv_stream);
806                 free_stream (&wvc_stream);
807                 return 1;
808             }
809 
810             free (filename_c);
811         }
812     }
813 
814     out_wpc = WavpackOpenFileOutput (write_block, &wv_stream, (wpconfig_flags & CONFIG_CREATE_WVC) ? &wvc_stream : NULL);
815 
816     if (!(test_flags & TEST_FLAG_NO_DECODE))
817         pthread_create (&pthread, NULL, decode_thread, (void *) &wv_decoder);
818 
819     if (test_flags & (TEST_FLAG_FLOAT_DATA | TEST_FLAG_STORE_INT32_AS_FLOAT)) {
820         wpconfig.float_norm_exp = 127;
821         wpconfig.bytes_per_sample = 4;
822         wpconfig.bits_per_sample = 32;
823     }
824     else {
825         wpconfig.bytes_per_sample = (bits + 7) >> 3;
826         wpconfig.bits_per_sample = bits;
827     }
828 
829     if (test_flags & TEST_FLAG_EXTRA_MASK) {
830         sprintf (mode_string + strlen (mode_string), "x%c", '0' + (test_flags & TEST_FLAG_EXTRA_MASK));
831         wpconfig.xmode = test_flags & TEST_FLAG_EXTRA_MASK;
832         wpconfig_flags |= CONFIG_EXTRA_MODE;
833     }
834 
835     wpconfig.sample_rate = SAMPLE_RATE;
836     wpconfig.num_channels = num_chans;
837     wpconfig.channel_mask = chan_mask;
838     wpconfig.flags = wpconfig_flags;
839 
840     if (wpconfig_flags & CONFIG_HYBRID_FLAG) {
841         if (wpconfig_flags & CONFIG_CREATE_WVC) {
842             if (test_flags & TEST_FLAG_IGNORE_WVC) {
843                 strcat (mode_string, "b4c");
844                 wpconfig.bitrate = 4.0;
845             }
846             else {
847                 strcat (mode_string, "b3c");
848                 wpconfig.bitrate = 3.0;
849             }
850         }
851         else {
852             strcat (mode_string, "b5");
853             wpconfig.bitrate = 5.0;
854         }
855     }
856 
857     WavpackSetConfiguration64 (out_wpc, &wpconfig, -1, NULL);
858     WavpackPackInit (out_wpc);
859 
860     while (seconds < num_seconds) {
861 
862         double translated_angle = cos (sequencing_angle) * 100.0;
863         double width_scalar = pow (2.0, -width);
864 
865         for (k = 0; k < num_chans; ++k) {
866             channels [k].audio_gain [0] = pow (sin (translated_angle + channels [k].angle_offset - M_PI * 1.6667) + 1.0, width) * width_scalar * NOISE_GAIN;
867             channels [k].audio_gain [1] = pow (sin (translated_angle + channels [k].angle_offset - M_PI * 0.6667) + 1.0, width) * width_scalar * TONE_GAIN;
868             channels [k].audio_gain [2] = pow (sin (translated_angle + channels [k].angle_offset - M_PI * 0.3333) + 1.0, width) * width_scalar * NOISE_GAIN;
869             channels [k].audio_gain [3] = pow (sin (translated_angle + channels [k].angle_offset - M_PI * 1.3333) + 1.0, width) * width_scalar * TONE_GAIN;
870             channels [k].audio_gain [4] = pow (sin (translated_angle + channels [k].angle_offset - M_PI) + 1.0, width) * width_scalar * NOISE_GAIN;
871             channels [k].audio_gain [5] = pow (sin (translated_angle + channels [k].angle_offset) + 1.0, width) * width_scalar * TONE_GAIN;
872         }
873 
874         memset (destin, 0, ENCODE_SAMPLES * num_chans * sizeof (*destin));
875 
876         for (j = 0; j < NUM_GENERATORS; ++j) {
877             audio_generator_run (&generators [j], source, ENCODE_SAMPLES);
878 
879             for (k = 0; k < num_chans; ++k) {
880                 if (!channels [k].lfe_flag || j < 2)
881                     mix_samples_with_gain (destin + k, source, ENCODE_SAMPLES, num_chans, channels [k].audio_gain_hist [j], channels [k].audio_gain [j]);
882 
883                 channels [k].audio_gain_hist [j] = channels [k].audio_gain [j];
884             }
885         }
886 
887         if (test_flags & TEST_FLAG_FLOAT_DATA) {
888             if (bits <= 25)
889                 truncate_float_samples (destin, ENCODE_SAMPLES * num_chans, bits);
890             else if (bits != 32) {
891                 printf ("invalid bits configuration\n");
892                 exit (-1);
893             }
894         }
895         else if (!(test_flags & TEST_FLAG_STORE_FLOAT_AS_INT32)) {
896             if (bits < 32)
897                 float_to_integer_samples (destin, ENCODE_SAMPLES * num_chans, bits);
898             else if (bits == 32)
899                 float_to_32bit_integer_samples (destin, ENCODE_SAMPLES * num_chans);
900             else {
901                 printf ("invalid bits configuration\n");
902                 exit (-1);
903             }
904         }
905 
906 	WavpackPackSamples (out_wpc, (int32_t *) destin, ENCODE_SAMPLES);
907         store_samples (destin, (int32_t *) destin, 0, wpconfig.bytes_per_sample, ENCODE_SAMPLES * num_chans);
908         MD5_Update (&md5_context, (unsigned char *) destin, wpconfig.bytes_per_sample * ENCODE_SAMPLES * num_chans);
909 
910         sequencing_angle += 2.0 * M_PI / SAMPLE_RATE / speed * ENCODE_SAMPLES;
911         if (sequencing_angle > M_PI) sequencing_angle -= M_PI * 2.0;
912 
913         if ((samples += ENCODE_SAMPLES) >= SAMPLE_RATE) {
914             samples -= SAMPLE_RATE;
915             ++seconds;
916 
917             if (!(wc & 1)) {
918                 if (width > 1.0) width *= 0.875;
919                 else if (width > 0.125) width -= 0.125;
920                 else {
921                     width = 0.0;
922                     wc++;
923                 }
924             }
925             else {
926                 if (width < 1.0) width += 0.125;
927                 else if (width < 200.0) width *= 1.125;
928                 else wc++;
929             }
930         }
931     }
932 
933     WavpackFlushSamples (out_wpc);
934     MD5_Final (md5_encoded, &md5_context);
935 
936     if (wpconfig.flags & CONFIG_MD5_CHECKSUM) {
937         WavpackStoreMD5Sum (out_wpc, md5_encoded);
938         WavpackFlushSamples (out_wpc);
939     }
940 
941     WavpackCloseFile (out_wpc);
942 
943     free (channels);
944     free (source);
945     free (destin);
946 
947     if ((wpconfig_flags & CONFIG_CREATE_WVC) && !(test_flags & TEST_FLAG_IGNORE_WVC))
948         total_encoded_bytes = wv_stream.bytes_written + wvc_stream.bytes_written;
949     else
950         total_encoded_bytes = wv_stream.bytes_written;
951 
952     total_encoded_samples = seconds * SAMPLE_RATE + samples;
953     ratio = total_encoded_bytes / ((float) total_encoded_samples * wpconfig.bytes_per_sample * num_chans);
954     bps = total_encoded_bytes * 8 / ((float) total_encoded_samples * num_chans);
955 
956     flush_stream (&wv_stream);
957     flush_stream (&wvc_stream);
958 
959     if (!(test_flags & TEST_FLAG_NO_DECODE)) {
960         pthread_join (pthread, &term_value);
961 
962         if (term_value) {
963             printf ("decode_thread() returned error %d\n", (int) (long) term_value);
964             return 1;
965         }
966     }
967 
968     if (!(test_flags & TEST_FLAG_NO_DECODE)) {
969         for (i = 0; i < 16; ++i) {
970             sprintf (md5_string1 + (i * 2), "%02x", md5_encoded [i]);
971             sprintf (md5_string2 + (i * 2), "%02x", wv_decoder.md5_decoded [i]);
972         }
973 
974         if (wv_decoder.num_errors || wv_decoder.sample_count != total_encoded_samples ||
975             (lossless && memcmp (md5_encoded, wv_decoder.md5_decoded, sizeof (md5_encoded)))) {
976                 printf ("\n---------------------------------------------\n");
977                 printf ("enc/dec sample count: %u / %u\n", total_encoded_samples, wv_decoder.sample_count);
978                 printf ("encoded md5: %s\n", md5_string1);
979                 printf ("decoded md5: %s\n", md5_string2);
980                 printf ("reported decode errors: %d\n", wv_decoder.num_errors);
981                 printf ("---------------------------------------------\n");
982                 return wv_decoder.num_errors + 1;
983         }
984     }
985 
986     free_stream (&wv_stream);
987     free_stream (&wvc_stream);
988 
989     printf ("pass (%8s, %.2f%%, %.2f bps, %s)\n", mode_string, 100.0 - ratio * 100.0, bps, md5_string2);
990 
991     return 0;
992 }
993 
994 // Thread / function that opens a virtual WavPack file, decodes it and calculates the MD5 hash of the
995 // decoded audio data.
996 
997 #define DECODE_SAMPLES 1000
998 
decode_thread(void * threadid)999 static void *decode_thread (void *threadid)
1000 {
1001     WavpackDecoder *wd = (WavpackDecoder *) threadid;
1002     char error [80];
1003     WavpackContext *wpc = WavpackOpenFileInputEx (&freader, wd->wv_stream, wd->wvc_stream, error, 0, 0);
1004     int32_t *decoded_samples, num_chans, bps;
1005     MD5_CTX md5_context;
1006 
1007     if (!wpc) {
1008         printf ("decode_thread(): error \"%s\" opening input file\n", error);
1009         wd->num_errors = 1;
1010         pthread_exit (NULL);
1011     }
1012 
1013     MD5_Init (&md5_context);
1014     num_chans = WavpackGetNumChannels (wpc);
1015     bps = WavpackGetBytesPerSample (wpc);
1016 
1017     decoded_samples = malloc (sizeof (int32_t) * DECODE_SAMPLES * num_chans);
1018 
1019     if (!decoded_samples) {
1020         printf ("decode_thread(): can't allocate memory!\n");
1021         exit (-1);
1022     }
1023 
1024     while (1) {
1025         int samples = WavpackUnpackSamples (wpc, decoded_samples, DECODE_SAMPLES);
1026 
1027         if (!samples)
1028             break;
1029 
1030         store_samples (decoded_samples, decoded_samples, 0, bps, samples * num_chans);
1031         MD5_Update (&md5_context, (unsigned char *) decoded_samples, bps * samples * num_chans);
1032         wd->sample_count += samples;
1033     }
1034 
1035     MD5_Final (wd->md5_decoded, &md5_context);
1036     wd->num_errors = WavpackGetNumErrors (wpc);
1037     free (decoded_samples);
1038     WavpackCloseFile (wpc);
1039     pthread_exit (NULL);
1040     return NULL;
1041 }
1042 
1043 // This code implements a simple virtual "file" so that we can have a WavPack encoding process and
1044 // a WavPack decoding process running at the same time (using Pthreads).
1045 
write_block(void * id,void * data,int32_t length)1046 static int write_block (void *id, void *data, int32_t length)
1047 {
1048     StreamingFile *ws = (StreamingFile *) id;
1049     unsigned char *data_ptr = data;
1050 
1051     if (!ws || !data || !length)
1052 	return 0;
1053 
1054 //    if (frandom() < .0001)
1055 //        ((char *) data) [(int) floor (length * frandom())] ^= 1;
1056 
1057     if (!ws->first_block_size)
1058         ws->first_block_size = length;
1059 
1060     ws->bytes_written += length;
1061 
1062     if (ws->file && !ws->error) {
1063         if (!fwrite (data, 1, length, ws->file)) {
1064             ws->error = 1;
1065             fclose (ws->file);
1066             ws->file = NULL;
1067         }
1068     }
1069 
1070     if (!ws->buffer_size)       // if no buffer, just swallow data silently
1071         return 1;
1072 
1073     pthread_mutex_lock (&ws->mutex);
1074 
1075     while (length) {
1076         int32_t bytes_available = ws->buffer_tail - ws->buffer_head - 1;
1077         int32_t bytes_to_copy = length;
1078 
1079         if (bytes_available < 0)
1080             bytes_available += ws->buffer_size;
1081 
1082         if (bytes_available < bytes_to_copy)
1083             bytes_to_copy = bytes_available;
1084 
1085         if (ws->buffer_head + bytes_to_copy > ws->buffer_base + ws->buffer_size)
1086             bytes_to_copy = ws->buffer_base + ws->buffer_size - ws->buffer_head;
1087 
1088         if (!bytes_to_copy) {
1089             ws->full_waits++;
1090             pthread_cond_wait (&ws->cond_read, &ws->mutex);
1091             continue;
1092         }
1093 
1094         memcpy ((void *) ws->buffer_head, data_ptr, bytes_to_copy);
1095 
1096         if ((ws->buffer_head += bytes_to_copy) == ws->buffer_base + ws->buffer_size)
1097             ws->buffer_head = ws->buffer_base;
1098 
1099         data_ptr += bytes_to_copy;
1100         length -= bytes_to_copy;
1101     }
1102 
1103     pthread_cond_signal (&ws->cond_write);
1104     pthread_mutex_unlock (&ws->mutex);
1105 
1106     return 1;
1107 }
1108 
read_bytes(void * id,void * data,int32_t bcount)1109 static int32_t read_bytes (void *id, void *data, int32_t bcount)
1110 {
1111     StreamingFile *ws = (StreamingFile *) id;
1112     unsigned char *data_ptr = data;
1113 
1114     pthread_mutex_lock (&ws->mutex);
1115 
1116     while (bcount) {
1117         if (ws->push_back) {
1118             *data_ptr++ = ws->push_back;
1119             ws->push_back = 0;
1120             bcount--;
1121         }
1122         else if (ws->buffer_head != ws->buffer_tail) {
1123             int bytes_available = ws->buffer_head - ws->buffer_tail;
1124             int32_t bytes_to_copy = bcount;
1125 
1126             if (bytes_available < 0)
1127                 bytes_available += ws->buffer_size;
1128 
1129             if (bytes_available < bytes_to_copy)
1130                 bytes_to_copy = bytes_available;
1131 
1132             if (ws->buffer_tail + bytes_to_copy > ws->buffer_base + ws->buffer_size)
1133                 bytes_to_copy = ws->buffer_base + ws->buffer_size - ws->buffer_tail;
1134 
1135             memcpy (data_ptr, (void *) ws->buffer_tail, bytes_to_copy);
1136 
1137             if ((ws->buffer_tail += bytes_to_copy) == ws->buffer_base + ws->buffer_size)
1138                 ws->buffer_tail = ws->buffer_base;
1139 
1140             ws->bytes_read += bytes_to_copy;
1141             data_ptr += bytes_to_copy;
1142             bcount -= bytes_to_copy;
1143         }
1144         else if (ws->done)
1145             break;
1146         else {
1147             ws->empty_waits++;
1148             pthread_cond_wait (&ws->cond_write, &ws->mutex);
1149         }
1150     }
1151 
1152     pthread_cond_signal (&ws->cond_read);
1153     pthread_mutex_unlock (&ws->mutex);
1154 
1155     return data_ptr - (unsigned char *) data;
1156 }
1157 
get_pos(void * id)1158 static uint32_t get_pos (void *id)
1159 {
1160     return -1;
1161 }
1162 
set_pos_abs(void * id,uint32_t pos)1163 static int set_pos_abs (void *id, uint32_t pos)
1164 {
1165     return 0;
1166 }
1167 
set_pos_rel(void * id,int32_t delta,int mode)1168 static int set_pos_rel (void *id, int32_t delta, int mode)
1169 {
1170     return -1;
1171 }
1172 
push_back_byte(void * id,int c)1173 static int push_back_byte (void *id, int c)
1174 {
1175     StreamingFile *ws = (StreamingFile *) id;
1176 
1177     if (!ws->push_back)
1178         return ws->push_back = c;
1179     else
1180         return EOF;
1181 }
1182 
get_length(void * id)1183 static uint32_t get_length (void *id)
1184 {
1185     return 0;
1186 }
1187 
can_seek(void * id)1188 static int can_seek (void *id)
1189 {
1190     return 0;
1191 }
1192 
1193 static WavpackStreamReader freader = {
1194     read_bytes, get_pos, set_pos_abs, set_pos_rel, push_back_byte, get_length, can_seek,
1195 };
1196 
initialize_stream(StreamingFile * ws,int buffer_size)1197 static void initialize_stream (StreamingFile *ws, int buffer_size)
1198 {
1199     if (buffer_size) {
1200         ws->buffer_base = malloc (ws->buffer_size = buffer_size);
1201         ws->buffer_head = ws->buffer_tail = ws->buffer_base;
1202         pthread_cond_init (&ws->cond_write, NULL);
1203         pthread_cond_init (&ws->cond_read, NULL);
1204         pthread_mutex_init (&ws->mutex, NULL);
1205     }
1206 }
1207 
flush_stream(StreamingFile * ws)1208 static void flush_stream (StreamingFile *ws)
1209 {
1210     if (ws->buffer_base) {
1211         pthread_mutex_lock (&ws->mutex);
1212         ws->done = 1;
1213         pthread_cond_signal (&ws->cond_write);
1214         pthread_mutex_unlock (&ws->mutex);
1215     }
1216 }
1217 
free_stream(StreamingFile * ws)1218 static void free_stream (StreamingFile *ws)
1219 {
1220     if (ws->file) {
1221         fclose (ws->file);
1222         ws->file = NULL;
1223     }
1224 
1225     if (ws->buffer_base) {
1226         free ((void *) ws->buffer_base);
1227         ws->buffer_base = NULL;
1228     }
1229 }
1230 
1231 // Helper utilities for generating the audio used for testing.
1232 
1233 // Return a random value in the range: 0.0 <= n < 1.0
1234 
frandom(void)1235 static double frandom (void)
1236 {
1237     static uint64_t random = 0x3141592653589793;
1238     random = ((random << 4) - random) ^ 1;
1239     random = ((random << 4) - random) ^ 1;
1240     random = ((random << 4) - random) ^ 1;
1241     return (random >> 32) / 4294967296.0;
1242 }
1243 
tone_generator_init(struct audio_generator * cxt,int sample_rate,int low_freq,int high_freq)1244 static void tone_generator_init (struct audio_generator *cxt, int sample_rate, int low_freq, int high_freq)
1245 {
1246     struct tone_generator *tone_cxt = &cxt->u.tone_cxt;
1247 
1248     memset (cxt, 0, sizeof (*cxt));
1249     cxt->type = tone;
1250 
1251     tone_cxt->sample_rate = sample_rate;
1252     tone_cxt->high_frequency = high_freq;
1253     tone_cxt->low_frequency = low_freq;
1254     tone_cxt->samples_per_update = sample_rate / low_freq * 4;
1255 }
1256 
tone_generator_run(struct tone_generator * cxt,float * samples,int num_samples)1257 static void tone_generator_run (struct tone_generator *cxt, float *samples, int num_samples)
1258 {
1259     float target_frequency, target_velocity;
1260 
1261     while (num_samples--) {
1262         if (!cxt->samples_left) {
1263             cxt->samples_left = cxt->samples_per_update;
1264 
1265             target_frequency = cxt->low_frequency * pow (cxt->high_frequency / cxt->low_frequency, frandom ());
1266             target_velocity = (M_PI * 2.0) / ((float) cxt->sample_rate / target_frequency);
1267             cxt->acceleration = (target_velocity - cxt->velocity) / cxt->samples_left;
1268         }
1269 
1270         *samples++ = sin (cxt->angle += cxt->velocity += cxt->acceleration);
1271         if (cxt->angle > M_PI) cxt->angle -= M_PI * 2.0;
1272         cxt->samples_left--;
1273     }
1274 }
1275 
noise_generator_init(struct audio_generator * cxt,float factor)1276 static void noise_generator_init (struct audio_generator *cxt, float factor)
1277 {
1278     struct noise_generator *noise_cxt = &cxt->u.noise_cxt;
1279 
1280     memset (cxt, 0, sizeof (*cxt));
1281     cxt->type = noise;
1282 
1283     noise_cxt->scalar = factor * factor * factor * sqrt (factor) / (2.0 + factor * factor);
1284     noise_cxt->factor = factor;
1285 }
1286 
noise_generator_run(struct noise_generator * cxt,float * samples,int num_samples)1287 static void noise_generator_run (struct noise_generator *cxt, float *samples, int num_samples)
1288 {
1289     while (num_samples--) {
1290         float source = (frandom () - 0.5) * cxt->scalar;
1291         cxt->sum1 += (source - cxt->sum1) / cxt->factor;
1292         cxt->sum2 += (cxt->sum1 - cxt->sum2) / cxt->factor;
1293         *samples++ = cxt->sum2 - cxt->sum2p;
1294         cxt->sum2p = cxt->sum2;
1295     }
1296 }
1297 
audio_generator_run(struct audio_generator * cxt,float * samples,int num_samples)1298 static void audio_generator_run (struct audio_generator *cxt, float *samples, int num_samples)
1299 {
1300     switch (cxt->type) {
1301         case noise:
1302             noise_generator_run (&cxt->u.noise_cxt, samples, num_samples);
1303             break;
1304 
1305         case tone:
1306             tone_generator_run (&cxt->u.tone_cxt, samples, num_samples);
1307             break;
1308 
1309         default:
1310             printf ("bad audio generator type!\n");
1311             exit (-1);
1312     }
1313 }
1314 
mix_samples_with_gain(float * destin,float * source,int num_samples,int num_chans,float initial_gain,float final_gain)1315 static void mix_samples_with_gain (float *destin, float *source, int num_samples, int num_chans, float initial_gain, float final_gain)
1316 {
1317     float delta_gain = (final_gain - initial_gain) / num_samples;
1318     float gain = initial_gain - delta_gain;
1319 
1320     while (num_samples--) {
1321         *destin += *source++ * (gain += delta_gain);
1322         destin += num_chans;
1323     }
1324 }
1325 
truncate_float_samples(float * samples,int num_samples,int bits)1326 static void truncate_float_samples (float *samples, int num_samples, int bits)
1327 {
1328     int isample, imin = -(1 << (bits - 1)), imax = (1 << (bits - 1)) - 1;
1329     float scalar = (float) (1 << (bits - 1));
1330 
1331     while (num_samples--) {
1332         if (*samples >= 1.0)
1333             isample = imax;
1334         else if (*samples <= -1.0)
1335             isample = imin;
1336         else
1337             isample = floor (*samples * scalar);
1338 
1339         *samples++ = isample / scalar;
1340     }
1341 }
1342 
float_to_integer_samples(float * samples,int num_samples,int bits)1343 static void float_to_integer_samples (float *samples, int num_samples, int bits)
1344 {
1345     int isample, imin = -(1 << (bits - 1)), imax = (1 << (bits - 1)) - 1;
1346     float scalar = (float) (1 << (bits - 1));
1347     int ishift = (8 - (bits & 0x7)) & 0x7;
1348 
1349     while (num_samples--) {
1350         if (*samples >= 1.0)
1351             isample = imax;
1352         else if (*samples <= -1.0)
1353             isample = imin;
1354         else
1355             isample = floor (*samples * scalar);
1356 
1357         *(int32_t *)samples = (uint32_t) isample << ishift;
1358         samples++;
1359     }
1360 }
1361 
float_to_32bit_integer_samples(float * samples,int num_samples)1362 static void float_to_32bit_integer_samples (float *samples, int num_samples)
1363 {
1364     int isample, imin = 0x8000000, imax = 0x7fffffff;
1365     float scalar = 2147483648.0;
1366 
1367     while (num_samples--) {
1368         if (*samples >= 1.0)
1369             isample = imax;
1370         else if (*samples <= -1.0)
1371             isample = imin;
1372         else
1373             isample = floor (*samples * scalar);
1374 
1375         // if there are trailing zeros, fill them in with random data
1376 
1377         if (isample && !(isample & 1)) {
1378             int tzeros = 1;
1379 
1380             while (!((isample >>= 1) & 1))
1381                 tzeros++;
1382 
1383             while (tzeros--)
1384                 isample = ((unsigned int) isample << 1) + ((frandom() > 0.5) ? 1 : 0);
1385         }
1386 
1387         *(int32_t *)samples = isample;
1388         samples++;
1389     }
1390 }
1391 
1392 // Code to store samples. Source is an array of int32_t data (which is what WavPack uses
1393 // internally), but the destination can have from 1 to 4 bytes per sample. Also, the destination
1394 // data is assumed to be little-endian and signed, except for byte data which is unsigned (these
1395 // are WAV file defaults). The endian and signedness can be overridden with the qmode flags
1396 // to support other formats.
1397 
1398 static void *store_little_endian_unsigned_samples (void *dst, int32_t *src, int bps, int count);
1399 static void *store_little_endian_signed_samples (void *dst, int32_t *src, int bps, int count);
1400 static void *store_big_endian_unsigned_samples (void *dst, int32_t *src, int bps, int count);
1401 static void *store_big_endian_signed_samples (void *dst, int32_t *src, int bps, int count);
1402 
store_samples(void * dst,int32_t * src,int qmode,int bps,int count)1403 static void *store_samples (void *dst, int32_t *src, int qmode, int bps, int count)
1404 {
1405     if (qmode & QMODE_BIG_ENDIAN) {
1406         if ((qmode & QMODE_UNSIGNED_WORDS) || (bps == 1 && !(qmode & QMODE_SIGNED_BYTES)))
1407             return store_big_endian_unsigned_samples (dst, src, bps, count);
1408         else
1409             return store_big_endian_signed_samples (dst, src, bps, count);
1410     }
1411     else if ((qmode & QMODE_UNSIGNED_WORDS) || (bps == 1 && !(qmode & (QMODE_SIGNED_BYTES | QMODE_DSD_AUDIO))))
1412         return store_little_endian_unsigned_samples (dst, src, bps, count);
1413     else
1414         return store_little_endian_signed_samples (dst, src, bps, count);
1415 }
1416 
store_little_endian_unsigned_samples(void * dst,int32_t * src,int bps,int count)1417 static void *store_little_endian_unsigned_samples (void *dst, int32_t *src, int bps, int count)
1418 {
1419     unsigned char *dptr = dst;
1420     int32_t temp;
1421 
1422     switch (bps) {
1423 
1424         case 1:
1425             while (count--)
1426                 *dptr++ = *src++ + 0x80;
1427 
1428             break;
1429 
1430         case 2:
1431             while (count--) {
1432                 *dptr++ = (unsigned char) (temp = *src++ + 0x8000);
1433                 *dptr++ = (unsigned char) (temp >> 8);
1434             }
1435 
1436             break;
1437 
1438         case 3:
1439             while (count--) {
1440                 *dptr++ = (unsigned char) (temp = *src++ + 0x800000);
1441                 *dptr++ = (unsigned char) (temp >> 8);
1442                 *dptr++ = (unsigned char) (temp >> 16);
1443             }
1444 
1445             break;
1446 
1447         case 4:
1448             while (count--) {
1449                 *dptr++ = (unsigned char) (temp = *src++ + 0x80000000);
1450                 *dptr++ = (unsigned char) (temp >> 8);
1451                 *dptr++ = (unsigned char) (temp >> 16);
1452                 *dptr++ = (unsigned char) (temp >> 24);
1453             }
1454 
1455             break;
1456     }
1457 
1458     return dptr;
1459 }
1460 
store_little_endian_signed_samples(void * dst,int32_t * src,int bps,int count)1461 static void *store_little_endian_signed_samples (void *dst, int32_t *src, int bps, int count)
1462 {
1463     unsigned char *dptr = dst;
1464     int32_t temp;
1465 
1466     switch (bps) {
1467 
1468         case 1:
1469             while (count--)
1470                 *dptr++ = *src++;
1471 
1472             break;
1473 
1474         case 2:
1475             while (count--) {
1476                 *dptr++ = (unsigned char) (temp = *src++);
1477                 *dptr++ = (unsigned char) (temp >> 8);
1478             }
1479 
1480             break;
1481 
1482         case 3:
1483             while (count--) {
1484                 *dptr++ = (unsigned char) (temp = *src++);
1485                 *dptr++ = (unsigned char) (temp >> 8);
1486                 *dptr++ = (unsigned char) (temp >> 16);
1487             }
1488 
1489             break;
1490 
1491         case 4:
1492             while (count--) {
1493                 *dptr++ = (unsigned char) (temp = *src++);
1494                 *dptr++ = (unsigned char) (temp >> 8);
1495                 *dptr++ = (unsigned char) (temp >> 16);
1496                 *dptr++ = (unsigned char) (temp >> 24);
1497             }
1498 
1499             break;
1500     }
1501 
1502     return dptr;
1503 }
1504 
store_big_endian_unsigned_samples(void * dst,int32_t * src,int bps,int count)1505 static void *store_big_endian_unsigned_samples (void *dst, int32_t *src, int bps, int count)
1506 {
1507     unsigned char *dptr = dst;
1508     int32_t temp;
1509 
1510     switch (bps) {
1511 
1512         case 1:
1513             while (count--)
1514                 *dptr++ = *src++ + 0x80;
1515 
1516             break;
1517 
1518         case 2:
1519             while (count--) {
1520                 *dptr++ = (unsigned char) ((temp = *src++ + 0x8000) >> 8);
1521                 *dptr++ = (unsigned char) temp;
1522             }
1523 
1524             break;
1525 
1526         case 3:
1527             while (count--) {
1528                 *dptr++ = (unsigned char) ((temp = *src++ + 0x800000) >> 16);
1529                 *dptr++ = (unsigned char) (temp >> 8);
1530                 *dptr++ = (unsigned char) temp;
1531             }
1532 
1533             break;
1534 
1535         case 4:
1536             while (count--) {
1537                 *dptr++ = (unsigned char) ((temp = *src++ + 0x80000000) >> 24);
1538                 *dptr++ = (unsigned char) (temp >> 16);
1539                 *dptr++ = (unsigned char) (temp >> 8);
1540                 *dptr++ = (unsigned char) temp;
1541             }
1542 
1543             break;
1544     }
1545 
1546     return dptr;
1547 }
1548 
store_big_endian_signed_samples(void * dst,int32_t * src,int bps,int count)1549 static void *store_big_endian_signed_samples (void *dst, int32_t *src, int bps, int count)
1550 {
1551     unsigned char *dptr = dst;
1552     int32_t temp;
1553 
1554     switch (bps) {
1555 
1556         case 1:
1557             while (count--)
1558                 *dptr++ = *src++;
1559 
1560             break;
1561 
1562         case 2:
1563             while (count--) {
1564                 *dptr++ = (unsigned char) ((temp = *src++) >> 8);
1565                 *dptr++ = (unsigned char) temp;
1566             }
1567 
1568             break;
1569 
1570         case 3:
1571             while (count--) {
1572                 *dptr++ = (unsigned char) ((temp = *src++) >> 16);
1573                 *dptr++ = (unsigned char) (temp >> 8);
1574                 *dptr++ = (unsigned char) temp;
1575             }
1576 
1577             break;
1578 
1579         case 4:
1580             while (count--) {
1581                 *dptr++ = (unsigned char) ((temp = *src++) >> 24);
1582                 *dptr++ = (unsigned char) (temp >> 16);
1583                 *dptr++ = (unsigned char) (temp >> 8);
1584                 *dptr++ = (unsigned char) temp;
1585             }
1586 
1587             break;
1588     }
1589 
1590     return dptr;
1591 }
1592