furnace/extern/adpcm-xq-s/adpcm-xq.c

820 lines
30 KiB
C
Raw Normal View History

2024-04-05 19:45:41 +00:00
#ifndef ARDUINO
////////////////////////////////////////////////////////////////////////////
// **** ADPCM-XQ **** //
// Xtreme Quality ADPCM Encoder/Decoder //
// Copyright (c) 2022 David Bryant. //
// All Rights Reserved. //
// Distributed under the BSD Software License (see license.txt) //
////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "adpcm-lib.h"
// This runtime macro is not strictly needed because the code is endian-safe,
// but including it improves performance on little-endian systems because we
// can avoid a couple loops through the audio.
#define IS_BIG_ENDIAN (*(uint16_t *)"\0\xff" < 0x0100)
static const char *sign_on = "\n"
" ADPCM-XQ Xtreme Quality IMA-ADPCM WAV Encoder / Decoder Version 0.4\n"
" Copyright (c) 2022 David Bryant. All Rights Reserved.\n\n";
static const char *usage =
" Usage: ADPCM-XQ [-options] infile.wav outfile.wav\n\n"
" Operation: conversion is performed based on the type of the infile\n"
" (either encode 16-bit PCM to 4-bit IMA-ADPCM or decode back)\n\n"
" Options: -[0-8] = encode lookahead samples (default = 3)\n"
" -bn = override auto block size, 2^n bytes (n = 8-15)\n"
" -d = decode only (fail on WAV file already PCM)\n"
" -e = encode only (fail on WAV file already ADPCM)\n"
" -f = encode flat noise (no dynamic noise shaping)\n"
" -h = display this help message\n"
" -q = quiet mode (display errors only)\n"
" -r = raw output (little-endian, no WAV header written)\n"
" -v = verbose (display lots of info)\n"
" -y = overwrite outfile if it exists\n\n"
" Web: Visit www.github.com/dbry/adpcm-xq for latest version and info\n\n";
#define ADPCM_FLAG_NOISE_SHAPING 0x1
#define ADPCM_FLAG_RAW_OUTPUT 0x2
static int adpcm_converter (char *infilename, char *outfilename, int flags, int blocksize_pow2, int lookahead);
static int verbosity = 0, decode_only = 0, encode_only = 0;
int main (argc, argv) int argc; char **argv;
{
int lookahead = 3, flags = ADPCM_FLAG_NOISE_SHAPING, blocksize_pow2 = 0, overwrite = 0, asked_help = 0;
char *infilename = NULL, *outfilename = NULL;
FILE *outfile;
// if the name of the executable ends in "encoder" or "decoder", just do that function
encode_only = argc && strstr (argv [0], "encoder") && strlen (strstr (argv [0], "encoder")) == strlen ("encoder");
decode_only = argc && strstr (argv [0], "decoder") && strlen (strstr (argv [0], "decoder")) == strlen ("decoder");
// loop through command-line arguments
while (--argc) {
#if defined (_WIN32)
if ((**++argv == '-' || **argv == '/') && (*argv)[1])
#else
if ((**++argv == '-') && (*argv)[1])
#endif
while (*++*argv)
switch (**argv) {
case '0': case '1': case '2':
case '3': case '4': case '5':
case '6': case '7': case '8':
lookahead = **argv - '0';
break;
case 'B': case 'b':
blocksize_pow2 = strtol (++*argv, argv, 10);
if (blocksize_pow2 < 8 || blocksize_pow2 > 15) {
fprintf (stderr, "\nblock size power must be 8 to 15!\n");
return -1;
}
--*argv;
break;
case 'D': case 'd':
decode_only = 1;
break;
case 'E': case 'e':
encode_only = 1;
break;
case 'F': case 'f':
flags &= ~ADPCM_FLAG_NOISE_SHAPING;
break;
case 'H': case 'h':
asked_help = 0;
break;
case 'Q': case 'q':
verbosity = -1;
break;
case 'R': case 'r':
flags |= ADPCM_FLAG_RAW_OUTPUT;
break;
case 'V': case 'v':
verbosity = 1;
break;
case 'Y': case 'y':
overwrite = 1;
break;
default:
fprintf (stderr, "\nillegal option: %c !\n", **argv);
return 1;
}
else if (!infilename) {
infilename = malloc (strlen (*argv) + 10);
strcpy (infilename, *argv);
}
else if (!outfilename) {
outfilename = malloc (strlen (*argv) + 10);
strcpy (outfilename, *argv);
}
else {
fprintf (stderr, "\nextra unknown argument: %s !\n", *argv);
return 1;
}
}
if (verbosity >= 0)
fprintf (stderr, "%s", sign_on);
if (!outfilename || asked_help) {
printf ("%s", usage);
return 0;
}
if (!strcmp (infilename, outfilename)) {
fprintf (stderr, "can't overwrite input file (specify different/new output file name)\n");
return -1;
}
if (!overwrite && (outfile = fopen (outfilename, "r"))) {
fclose (outfile);
fprintf (stderr, "output file \"%s\" exists (use -y to overwrite)\n", outfilename);
return -1;
}
return adpcm_converter (infilename, outfilename, flags, blocksize_pow2, lookahead);
}
typedef struct {
char ckID [4];
uint32_t ckSize;
char formType [4];
} RiffChunkHeader;
typedef struct {
char ckID [4];
uint32_t ckSize;
} ChunkHeader;
#define ChunkHeaderFormat "4L"
typedef struct {
uint16_t FormatTag, NumChannels;
uint32_t SampleRate, BytesPerSecond;
uint16_t BlockAlign, BitsPerSample;
uint16_t cbSize;
union {
uint16_t ValidBitsPerSample;
uint16_t SamplesPerBlock;
uint16_t Reserved;
} Samples;
int32_t ChannelMask;
uint16_t SubFormat;
char GUID [14];
} WaveHeader;
#define WaveHeaderFormat "SSLLSSSSLS"
typedef struct {
char ckID [4];
uint32_t ckSize;
uint32_t TotalSamples;
} FactHeader;
#define FactHeaderFormat "4LL"
#define WAVE_FORMAT_PCM 0x1
#define WAVE_FORMAT_IMA_ADPCM 0x11
#define WAVE_FORMAT_EXTENSIBLE 0xfffe
static int write_pcm_wav_header (FILE *outfile, int num_channels, uint32_t num_samples, uint32_t sample_rate);
static int write_adpcm_wav_header (FILE *outfile, int num_channels, uint32_t num_samples, uint32_t sample_rate, int samples_per_block);
static int adpcm_decode_data (FILE *infile, FILE *outfile, int num_channels, uint32_t num_samples, int block_size);
static int adpcm_encode_data (FILE *infile, FILE *outfile, int num_channels, uint32_t num_samples, int samples_per_block, int lookahead, int noise_shaping);
static void little_endian_to_native (void *data, char *format);
static void native_to_little_endian (void *data, char *format);
static int adpcm_converter (char *infilename, char *outfilename, int flags, int blocksize_pow2, int lookahead)
{
int format = 0, res = 0, bits_per_sample, num_channels;
uint32_t fact_samples = 0, num_samples = 0, sample_rate;
FILE *infile, *outfile;
RiffChunkHeader riff_chunk_header;
ChunkHeader chunk_header;
WaveHeader WaveHeader;
if (!(infile = fopen (infilename, "rb"))) {
fprintf (stderr, "can't open file \"%s\" for reading!\n", infilename);
return -1;
}
// read initial RIFF form header
if (!fread (&riff_chunk_header, sizeof (RiffChunkHeader), 1, infile) ||
strncmp (riff_chunk_header.ckID, "RIFF", 4) ||
strncmp (riff_chunk_header.formType, "WAVE", 4)) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
// loop through all elements of the RIFF wav header (until the data chuck)
while (1) {
if (!fread (&chunk_header, sizeof (ChunkHeader), 1, infile)) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
little_endian_to_native (&chunk_header, ChunkHeaderFormat);
// if it's the format chunk, we want to get some info out of there and
// make sure it's a .wav file we can handle
if (!strncmp (chunk_header.ckID, "fmt ", 4)) {
int supported = 1;
if (chunk_header.ckSize < 16 || chunk_header.ckSize > sizeof (WaveHeader) ||
!fread (&WaveHeader, chunk_header.ckSize, 1, infile)) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
little_endian_to_native (&WaveHeader, WaveHeaderFormat);
format = (WaveHeader.FormatTag == WAVE_FORMAT_EXTENSIBLE && chunk_header.ckSize == 40) ?
WaveHeader.SubFormat : WaveHeader.FormatTag;
bits_per_sample = (chunk_header.ckSize == 40 && WaveHeader.Samples.ValidBitsPerSample) ?
WaveHeader.Samples.ValidBitsPerSample : WaveHeader.BitsPerSample;
if (WaveHeader.NumChannels < 1 || WaveHeader.NumChannels > 2)
supported = 0;
else if (format == WAVE_FORMAT_PCM) {
if (decode_only) {
fprintf (stderr, "\"%s\" is PCM .WAV file, invalid in decode-only mode!\n", infilename);
return -1;
}
if (bits_per_sample < 9 || bits_per_sample > 16)
supported = 0;
if (WaveHeader.BlockAlign != WaveHeader.NumChannels * 2)
supported = 0;
}
else if (format == WAVE_FORMAT_IMA_ADPCM) {
if (encode_only) {
fprintf (stderr, "\"%s\" is ADPCM .WAV file, invalid in encode-only mode!\n", infilename);
return -1;
}
if (bits_per_sample != 4)
supported = 0;
if (WaveHeader.Samples.SamplesPerBlock != (WaveHeader.BlockAlign - WaveHeader.NumChannels * 4) * (WaveHeader.NumChannels ^ 3) + 1) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
}
else
supported = 0;
if (!supported) {
fprintf (stderr, "\"%s\" is an unsupported .WAV format!\n", infilename);
return -1;
}
if (verbosity > 0) {
fprintf (stderr, "format tag size = %d\n", chunk_header.ckSize);
fprintf (stderr, "FormatTag = 0x%x, NumChannels = %u, BitsPerSample = %u\n",
WaveHeader.FormatTag, WaveHeader.NumChannels, WaveHeader.BitsPerSample);
fprintf (stderr, "BlockAlign = %u, SampleRate = %lu, BytesPerSecond = %lu\n",
WaveHeader.BlockAlign, (unsigned long) WaveHeader.SampleRate, (unsigned long) WaveHeader.BytesPerSecond);
if (chunk_header.ckSize > 16) {
if (format == WAVE_FORMAT_PCM)
fprintf (stderr, "cbSize = %d, ValidBitsPerSample = %d\n", WaveHeader.cbSize,
WaveHeader.Samples.ValidBitsPerSample);
else if (format == WAVE_FORMAT_IMA_ADPCM)
fprintf (stderr, "cbSize = %d, SamplesPerBlock = %d\n", WaveHeader.cbSize,
WaveHeader.Samples.SamplesPerBlock);
}
if (chunk_header.ckSize > 20)
fprintf (stderr, "ChannelMask = %x, SubFormat = %d\n",
WaveHeader.ChannelMask, WaveHeader.SubFormat);
}
}
else if (!strncmp (chunk_header.ckID, "fact", 4)) {
if (chunk_header.ckSize < 4 || !fread (&fact_samples, sizeof (fact_samples), 1, infile)) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
little_endian_to_native (&fact_samples, "L");
if (chunk_header.ckSize > 4) {
int bytes_to_skip = chunk_header.ckSize - 4;
char dummy;
while (bytes_to_skip--)
if (!fread (&dummy, 1, 1, infile)) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
}
}
else if (!strncmp (chunk_header.ckID, "data", 4)) {
// on the data chunk, get size and exit parsing loop
if (!WaveHeader.NumChannels) { // make sure we saw a "fmt" chunk...
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
if (!chunk_header.ckSize) {
fprintf (stderr, "this .WAV file has no audio samples, probably is corrupt!\n");
return -1;
}
if (format == WAVE_FORMAT_PCM) {
if (chunk_header.ckSize % WaveHeader.BlockAlign) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
num_samples = chunk_header.ckSize / WaveHeader.BlockAlign;
}
else {
uint32_t complete_blocks = chunk_header.ckSize / WaveHeader.BlockAlign;
int leftover_bytes = chunk_header.ckSize % WaveHeader.BlockAlign;
int samples_last_block;
num_samples = complete_blocks * WaveHeader.Samples.SamplesPerBlock;
if (leftover_bytes) {
if (leftover_bytes % (WaveHeader.NumChannels * 4)) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
if (verbosity > 0) fprintf (stderr, "data chunk has %d bytes left over for final ADPCM block\n", leftover_bytes);
samples_last_block = (leftover_bytes - (WaveHeader.NumChannels * 4)) * (WaveHeader.NumChannels ^ 3) + 1;
num_samples += samples_last_block;
}
else
samples_last_block = WaveHeader.Samples.SamplesPerBlock;
if (fact_samples) {
if (fact_samples < num_samples && fact_samples > num_samples - samples_last_block) {
if (verbosity > 0) fprintf (stderr, "total samples reduced %lu by FACT chunk\n", (unsigned long) (num_samples - fact_samples));
num_samples = fact_samples;
}
else if (WaveHeader.NumChannels == 2 && (fact_samples >>= 1) < num_samples && fact_samples > num_samples - samples_last_block) {
if (verbosity > 0) fprintf (stderr, "num samples reduced %lu by [incorrect] FACT chunk\n", (unsigned long) (num_samples - fact_samples));
num_samples = fact_samples;
}
}
}
if (!num_samples) {
fprintf (stderr, "this .WAV file has no audio samples, probably is corrupt!\n");
return -1;
}
if (verbosity > 0)
fprintf (stderr, "num samples = %lu\n", (unsigned long) num_samples);
num_channels = WaveHeader.NumChannels;
sample_rate = WaveHeader.SampleRate;
break;
}
else { // just ignore unknown chunks
int bytes_to_eat = (chunk_header.ckSize + 1) & ~1L;
char dummy;
if (verbosity > 0)
fprintf (stderr, "extra unknown chunk \"%c%c%c%c\" of %d bytes\n",
chunk_header.ckID [0], chunk_header.ckID [1], chunk_header.ckID [2],
chunk_header.ckID [3], chunk_header.ckSize);
while (bytes_to_eat--)
if (!fread (&dummy, 1, 1, infile)) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
}
}
if (!(outfile = fopen (outfilename, "wb"))) {
fprintf (stderr, "can't open file \"%s\" for writing!\n", outfilename);
return -1;
}
if (format == WAVE_FORMAT_PCM) {
int block_size, samples_per_block;
if (blocksize_pow2)
block_size = 1 << blocksize_pow2;
else
block_size = 256 * num_channels * (sample_rate < 11000 ? 1 : sample_rate / 11000);
samples_per_block = (block_size - num_channels * 4) * (num_channels ^ 3) + 1;
if (verbosity > 0)
fprintf (stderr, "each %d byte ADPCM block will contain %d samples * %d channels\n",
block_size, samples_per_block, num_channels);
if (!(flags & ADPCM_FLAG_RAW_OUTPUT) && !write_adpcm_wav_header (outfile, num_channels, num_samples, sample_rate, samples_per_block)) {
fprintf (stderr, "can't write header to file \"%s\" !\n", outfilename);
return -1;
}
if (verbosity >= 0) fprintf (stderr, "encoding PCM file \"%s\" to%sADPCM file \"%s\"...\n",
infilename, (flags & ADPCM_FLAG_RAW_OUTPUT) ? " raw " : " ", outfilename);
res = adpcm_encode_data (infile, outfile, num_channels, num_samples, samples_per_block, lookahead,
(flags & ADPCM_FLAG_NOISE_SHAPING) ? (sample_rate > 64000 ? NOISE_SHAPING_STATIC : NOISE_SHAPING_DYNAMIC) : NOISE_SHAPING_OFF);
}
else if (format == WAVE_FORMAT_IMA_ADPCM) {
if (!(flags & ADPCM_FLAG_RAW_OUTPUT) && !write_pcm_wav_header (outfile, num_channels, num_samples, sample_rate)) {
fprintf (stderr, "can't write header to file \"%s\" !\n", outfilename);
return -1;
}
if (verbosity >= 0) fprintf (stderr, "decoding ADPCM file \"%s\" to%sPCM file \"%s\"...\n",
infilename, (flags & ADPCM_FLAG_RAW_OUTPUT) ? " raw " : " ", outfilename);
res = adpcm_decode_data (infile, outfile, num_channels, num_samples, WaveHeader.BlockAlign);
}
fclose (outfile);
fclose (infile);
return res;
}
static int write_pcm_wav_header (FILE *outfile, int num_channels, uint32_t num_samples, uint32_t sample_rate)
{
RiffChunkHeader riffhdr;
ChunkHeader datahdr, fmthdr;
WaveHeader wavhdr;
int wavhdrsize = 16;
int bytes_per_sample = 2;
uint32_t total_data_bytes = num_samples * bytes_per_sample * num_channels;
memset (&wavhdr, 0, sizeof (wavhdr));
wavhdr.FormatTag = WAVE_FORMAT_PCM;
wavhdr.NumChannels = num_channels;
wavhdr.SampleRate = sample_rate;
wavhdr.BytesPerSecond = sample_rate * num_channels * bytes_per_sample;
wavhdr.BlockAlign = bytes_per_sample * num_channels;
wavhdr.BitsPerSample = 16;
strncpy (riffhdr.ckID, "RIFF", sizeof (riffhdr.ckID));
strncpy (riffhdr.formType, "WAVE", sizeof (riffhdr.formType));
riffhdr.ckSize = sizeof (riffhdr) + wavhdrsize + sizeof (datahdr) + total_data_bytes;
strncpy (fmthdr.ckID, "fmt ", sizeof (fmthdr.ckID));
fmthdr.ckSize = wavhdrsize;
strncpy (datahdr.ckID, "data", sizeof (datahdr.ckID));
datahdr.ckSize = total_data_bytes;
// write the RIFF chunks up to just before the data starts
native_to_little_endian (&riffhdr, ChunkHeaderFormat);
native_to_little_endian (&fmthdr, ChunkHeaderFormat);
native_to_little_endian (&wavhdr, WaveHeaderFormat);
native_to_little_endian (&datahdr, ChunkHeaderFormat);
return fwrite (&riffhdr, sizeof (riffhdr), 1, outfile) &&
fwrite (&fmthdr, sizeof (fmthdr), 1, outfile) &&
fwrite (&wavhdr, wavhdrsize, 1, outfile) &&
fwrite (&datahdr, sizeof (datahdr), 1, outfile);
}
static int write_adpcm_wav_header (FILE *outfile, int num_channels, uint32_t num_samples, uint32_t sample_rate, int samples_per_block)
{
RiffChunkHeader riffhdr;
ChunkHeader datahdr, fmthdr;
WaveHeader wavhdr;
FactHeader facthdr;
int wavhdrsize = 20;
int block_size = (samples_per_block - 1) / (num_channels ^ 3) + (num_channels * 4);
uint32_t num_blocks = num_samples / samples_per_block;
int leftover_samples = num_samples % samples_per_block;
uint32_t total_data_bytes = num_blocks * block_size;
if (leftover_samples) {
int last_block_samples = ((leftover_samples + 6) & ~7) + 1;
int last_block_size = (last_block_samples - 1) / (num_channels ^ 3) + (num_channels * 4);
total_data_bytes += last_block_size;
}
memset (&wavhdr, 0, sizeof (wavhdr));
wavhdr.FormatTag = WAVE_FORMAT_IMA_ADPCM;
wavhdr.NumChannels = num_channels;
wavhdr.SampleRate = sample_rate;
wavhdr.BytesPerSecond = sample_rate * block_size / samples_per_block;
wavhdr.BlockAlign = block_size;
wavhdr.BitsPerSample = 4;
wavhdr.cbSize = 2;
wavhdr.Samples.SamplesPerBlock = samples_per_block;
strncpy (riffhdr.ckID, "RIFF", sizeof (riffhdr.ckID));
strncpy (riffhdr.formType, "WAVE", sizeof (riffhdr.formType));
riffhdr.ckSize = sizeof (riffhdr) + wavhdrsize + sizeof (facthdr) + sizeof (datahdr) + total_data_bytes;
strncpy (fmthdr.ckID, "fmt ", sizeof (fmthdr.ckID));
fmthdr.ckSize = wavhdrsize;
strncpy (facthdr.ckID, "fact", sizeof (facthdr.ckID));
facthdr.TotalSamples = num_samples;
facthdr.ckSize = 4;
strncpy (datahdr.ckID, "data", sizeof (datahdr.ckID));
datahdr.ckSize = total_data_bytes;
// write the RIFF chunks up to just before the data starts
native_to_little_endian (&riffhdr, ChunkHeaderFormat);
native_to_little_endian (&fmthdr, ChunkHeaderFormat);
native_to_little_endian (&wavhdr, WaveHeaderFormat);
native_to_little_endian (&facthdr, FactHeaderFormat);
native_to_little_endian (&datahdr, ChunkHeaderFormat);
return fwrite (&riffhdr, sizeof (riffhdr), 1, outfile) &&
fwrite (&fmthdr, sizeof (fmthdr), 1, outfile) &&
fwrite (&wavhdr, wavhdrsize, 1, outfile) &&
fwrite (&facthdr, sizeof (facthdr), 1, outfile) &&
fwrite (&datahdr, sizeof (datahdr), 1, outfile);
}
static int adpcm_decode_data (FILE *infile, FILE *outfile, int num_channels, uint32_t num_samples, int block_size)
{
int samples_per_block = (block_size - num_channels * 4) * (num_channels ^ 3) + 1, percent;
void *pcm_block = malloc (samples_per_block * num_channels * 2);
void *adpcm_block = malloc (block_size);
uint32_t progress_divider = 0;
if (!pcm_block || !adpcm_block) {
fprintf (stderr, "could not allocate memory for buffers!\n");
return -1;
}
if (verbosity >= 0 && num_samples > 1000) {
progress_divider = (num_samples + 50) / 100;
fprintf (stderr, "\rprogress: %d%% ", percent = 0);
fflush (stderr);
}
while (num_samples) {
int this_block_adpcm_samples = samples_per_block;
int this_block_pcm_samples = samples_per_block;
if (this_block_adpcm_samples > num_samples) {
this_block_adpcm_samples = ((num_samples + 6) & ~7) + 1;
block_size = (this_block_adpcm_samples - 1) / (num_channels ^ 3) + (num_channels * 4);
this_block_pcm_samples = num_samples;
}
if (!fread (adpcm_block, block_size, 1, infile)) {
fprintf (stderr, "could not read all audio data from input file!\n");
return -1;
}
if (adpcm_decode_block (pcm_block, adpcm_block, block_size, num_channels) != this_block_adpcm_samples) {
fprintf (stderr, "adpcm_decode_block() did not return expected value!\n");
return -1;
}
if (IS_BIG_ENDIAN) {
int scount = this_block_pcm_samples * num_channels;
unsigned char *cp = (unsigned char *) pcm_block;
while (scount--) {
int16_t temp = * (int16_t *) cp;
*cp++ = (unsigned char) temp;
*cp++ = (unsigned char) (temp >> 8);
}
}
if (!fwrite (pcm_block, this_block_pcm_samples * num_channels * 2, 1, outfile)) {
fprintf (stderr, "could not write all audio data to output file!\n");
return -1;
}
num_samples -= this_block_pcm_samples;
if (progress_divider) {
int new_percent = 100 - num_samples / progress_divider;
if (new_percent != percent) {
fprintf (stderr, "\rprogress: %d%% ", percent = new_percent);
fflush (stderr);
}
}
}
if (verbosity >= 0)
fprintf (stderr, "\r...completed successfully\n");
free (adpcm_block);
free (pcm_block);
return 0;
}
static int adpcm_encode_data (FILE *infile, FILE *outfile, int num_channels, uint32_t num_samples, int samples_per_block, int lookahead, int noise_shaping)
{
int block_size = (samples_per_block - 1) / (num_channels ^ 3) + (num_channels * 4), percent;
int16_t *pcm_block = malloc (samples_per_block * num_channels * 2);
void *adpcm_block = malloc (block_size);
uint32_t progress_divider = 0;
void *adpcm_cnxt = NULL;
if (!pcm_block || !adpcm_block) {
fprintf (stderr, "could not allocate memory for buffers!\n");
return -1;
}
if (verbosity >= 0 && num_samples > 1000) {
progress_divider = (num_samples + 50) / 100;
fprintf (stderr, "\rprogress: %d%% ", percent = 0);
fflush (stderr);
}
while (num_samples) {
int this_block_adpcm_samples = samples_per_block;
int this_block_pcm_samples = samples_per_block;
size_t num_bytes;
if (this_block_pcm_samples > num_samples) {
this_block_adpcm_samples = ((num_samples + 6) & ~7) + 1;
block_size = (this_block_adpcm_samples - 1) / (num_channels ^ 3) + (num_channels * 4);
this_block_pcm_samples = num_samples;
}
if (!fread (pcm_block, this_block_pcm_samples * num_channels * 2, 1, infile)) {
fprintf (stderr, "\rcould not read all audio data from input file!\n");
return -1;
}
if (IS_BIG_ENDIAN) {
int scount = this_block_pcm_samples * num_channels;
unsigned char *cp = (unsigned char *) pcm_block;
while (scount--) {
int16_t temp = cp [0] + (cp [1] << 8);
* (int16_t *) cp = temp;
cp += 2;
}
}
// if this is the last block and it's not full, duplicate the last sample(s) so we don't
// create problems for the lookahead
if (this_block_adpcm_samples > this_block_pcm_samples) {
int16_t *dst = pcm_block + this_block_pcm_samples * num_channels, *src = dst - num_channels;
int dups = (this_block_adpcm_samples - this_block_pcm_samples) * num_channels;
while (dups--)
*dst++ = *src++;
}
// if this is the first block, compute a decaying average (in reverse) so that we can let the
// encoder know what kind of initial deltas to expect (helps initializing index)
if (!adpcm_cnxt) {
int32_t average_deltas [2];
int i;
average_deltas [0] = average_deltas [1] = 0;
for (i = this_block_adpcm_samples * num_channels; i -= num_channels;) {
average_deltas [0] -= average_deltas [0] >> 3;
average_deltas [0] += abs ((int32_t) pcm_block [i] - pcm_block [i - num_channels]);
if (num_channels == 2) {
average_deltas [1] -= average_deltas [1] >> 3;
average_deltas [1] += abs ((int32_t) pcm_block [i-1] - pcm_block [i+1]);
}
}
average_deltas [0] >>= 3;
average_deltas [1] >>= 3;
adpcm_cnxt = adpcm_create_context (num_channels, lookahead, noise_shaping, average_deltas);
}
adpcm_encode_block (adpcm_cnxt, adpcm_block, &num_bytes, pcm_block, this_block_adpcm_samples);
if (num_bytes != block_size) {
fprintf (stderr, "\radpcm_encode_block() did not return expected value (expected %d, got %d)!\n", block_size, (int) num_bytes);
return -1;
}
if (!fwrite (adpcm_block, block_size, 1, outfile)) {
fprintf (stderr, "\rcould not write all audio data to output file!\n");
return -1;
}
num_samples -= this_block_pcm_samples;
if (progress_divider) {
int new_percent = 100 - num_samples / progress_divider;
if (new_percent != percent) {
fprintf (stderr, "\rprogress: %d%% ", percent = new_percent);
fflush (stderr);
}
}
}
if (verbosity >= 0)
fprintf (stderr, "\r...completed successfully\n");
if (adpcm_cnxt)
adpcm_free_context (adpcm_cnxt);
free (adpcm_block);
free (pcm_block);
return 0;
}
static void little_endian_to_native (void *data, char *format)
{
unsigned char *cp = (unsigned char *) data;
int32_t temp;
while (*format) {
switch (*format) {
case 'L':
temp = cp [0] + ((int32_t) cp [1] << 8) + ((int32_t) cp [2] << 16) + ((int32_t) cp [3] << 24);
* (int32_t *) cp = temp;
cp += 4;
break;
case 'S':
temp = cp [0] + (cp [1] << 8);
* (short *) cp = (short) temp;
cp += 2;
break;
default:
if (isdigit ((unsigned char) *format))
cp += *format - '0';
break;
}
format++;
}
}
static void native_to_little_endian (void *data, char *format)
{
unsigned char *cp = (unsigned char *) data;
int32_t temp;
while (*format) {
switch (*format) {
case 'L':
temp = * (int32_t *) cp;
*cp++ = (unsigned char) temp;
*cp++ = (unsigned char) (temp >> 8);
*cp++ = (unsigned char) (temp >> 16);
*cp++ = (unsigned char) (temp >> 24);
break;
case 'S':
temp = * (short *) cp;
*cp++ = (unsigned char) temp;
*cp++ = (unsigned char) (temp >> 8);
break;
default:
if (isdigit ((unsigned char) *format))
cp += *format - '0';
break;
}
format++;
}
}
#endif