From a67d8b5f08a0a91913bf75dbc4f1beb5640c0504 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Wed, 29 Oct 2025 02:11:04 +0900 Subject: [PATCH] TAD: auto filename selection --- video_encoder/decoder_tad.c | 140 +++++++++++++++++++++---- video_encoder/encoder_tad.c | 24 ++--- video_encoder/encoder_tad_standalone.c | 43 +++++++- 3 files changed, 171 insertions(+), 36 deletions(-) diff --git a/video_encoder/decoder_tad.c b/video_encoder/decoder_tad.c index 74b01c1..a41ed81 100644 --- a/video_encoder/decoder_tad.c +++ b/video_encoder/decoder_tad.c @@ -22,16 +22,16 @@ static const float TAD32_COEFF_SCALARS[] = {64.0f, 45.255f, 32.0f, 22.627f, 16.0 // Linearly spaced from 1.0 (LL) to 2.0 (H9) // These weights are multiplied by quantiser_scale during dequantization static const float BASE_QUANTISER_WEIGHTS[] = { - 1.0f, // LL (L9) - finest preservation - 1.111f, // H (L9) - 1.222f, // H (L8) - 1.333f, // H (L7) - 1.444f, // H (L6) - 1.556f, // H (L5) - 1.667f, // H (L4) - 1.778f, // H (L3) - 1.889f, // H (L2) - 2.0f // H (L1) - coarsest quantization + 1.0f, // LL (L9) - finest preservation + 1.0f, // H (L9) + 1.0f, // H (L8) + 1.0f, // H (L7) + 1.0f, // H (L6) + 1.1f, // H (L5) + 1.2f, // H (L4) + 1.3f, // H (L3) + 1.4f, // H (L2) + 1.5f // H (L1) - coarsest quantization }; #define TAD_DEFAULT_CHUNK_SIZE 32768 @@ -52,6 +52,37 @@ static inline float FCLAMP(float x, float min, float max) { return x < min ? min : (x > max ? max : x); } +//============================================================================= +// WAV Header Writing +//============================================================================= + +static void write_wav_header(FILE *output, uint32_t data_size, uint16_t channels, uint32_t sample_rate, uint16_t bits_per_sample) { + uint32_t byte_rate = sample_rate * channels * bits_per_sample / 8; + uint16_t block_align = channels * bits_per_sample / 8; + uint32_t chunk_size = 36 + data_size; + + // RIFF header + fwrite("RIFF", 1, 4, output); + fwrite(&chunk_size, 4, 1, output); + fwrite("WAVE", 1, 4, output); + + // fmt chunk + fwrite("fmt ", 1, 4, output); + uint32_t fmt_size = 16; + fwrite(&fmt_size, 4, 1, output); + uint16_t audio_format = 1; // PCM + fwrite(&audio_format, 2, 1, output); + fwrite(&channels, 2, 1, output); + fwrite(&sample_rate, 4, 1, output); + fwrite(&byte_rate, 4, 1, output); + fwrite(&block_align, 2, 1, output); + fwrite(&bits_per_sample, 2, 1, output); + + // data chunk header + fwrite("data", 1, 4, output); + fwrite(&data_size, 4, 1, output); +} + // Calculate DWT levels from chunk size (must be power of 2, >= 1024) static int calculate_dwt_levels(int chunk_size) { /*if (chunk_size < TAD_MIN_CHUNK_SIZE) { @@ -287,9 +318,9 @@ static void expand_gamma(float *left, float *right, size_t count) { for (size_t i = 0; i < count; i++) { // decode(y) = sign(y) * |y|^(1/γ) where γ=0.5 float x = left[i]; float a = fabsf(x); - left[i] = signum(x) * a * a; + left[i] = signum(x) * powf(a, 1.4142f); float y = right[i]; float b = fabsf(y); - right[i] = signum(y) * b * b; + right[i] = signum(y) * powf(b, 1.4142f); } } @@ -514,24 +545,34 @@ static int decode_chunk(const uint8_t *input, size_t input_size, uint8_t *pcmu8_ //============================================================================= static void print_usage(const char *prog_name) { - printf("Usage: %s -i -o [options]\n", prog_name); + printf("Usage: %s -i [options]\n", prog_name); printf("Options:\n"); printf(" -i Input TAD file\n"); - printf(" -o Output PCMu8 file (raw 8-bit unsigned stereo @ 32kHz)\n"); + printf(" -o Output file (optional, auto-generated from input)\n"); + printf(" Default: input_qNN.wav (or .pcm with --raw-pcm)\n"); + printf(" --raw-pcm Output raw PCMu8 instead of WAV file\n"); printf(" -v Verbose output\n"); printf(" -h, --help Show this help\n"); printf("\nVersion: %s\n", DECODER_VENDOR_STRING); - printf("Output format: PCMu8 (unsigned 8-bit) stereo @ 32000 Hz\n"); - printf("To convert to WAV: ffmpeg -f u8 -ar 32000 -ac 2 -i output.raw output.wav\n"); + printf("Default output: WAV file (8-bit unsigned PCM, stereo @ 32000 Hz)\n"); + printf("With --raw-pcm: PCMu8 raw file (8-bit unsigned stereo @ 32000 Hz)\n"); } int main(int argc, char *argv[]) { char *input_file = NULL; char *output_file = NULL; int verbose = 0; + int raw_pcm = 0; + + static struct option long_options[] = { + {"raw-pcm", no_argument, 0, 'r'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; int opt; - while ((opt = getopt(argc, argv, "i:o:vh")) != -1) { + int option_index = 0; + while ((opt = getopt_long(argc, argv, "i:o:vh", long_options, &option_index)) != -1) { switch (opt) { case 'i': input_file = optarg; @@ -539,6 +580,9 @@ int main(int argc, char *argv[]) { case 'o': output_file = optarg; break; + case 'r': + raw_pcm = 1; + break; case 'v': verbose = 1; break; @@ -551,12 +595,52 @@ int main(int argc, char *argv[]) { } } - if (!input_file || !output_file) { - fprintf(stderr, "Error: Input and output files are required\n"); + if (!input_file) { + fprintf(stderr, "Error: Input file is required\n"); print_usage(argv[0]); return 1; } + // Generate output filename if not provided + if (!output_file) { + size_t input_len = strlen(input_file); + output_file = malloc(input_len + 32); // Extra space for extension + + // Find the last directory separator + const char *basename_start = strrchr(input_file, '/'); + if (!basename_start) basename_start = strrchr(input_file, '\\'); + basename_start = basename_start ? basename_start + 1 : input_file; + + // Copy directory part + size_t dir_len = basename_start - input_file; + strncpy(output_file, input_file, dir_len); + + // Find the .tad extension + const char *ext = strrchr(basename_start, '.'); + if (ext && strcmp(ext, ".tad") == 0) { + // Copy basename without .tad + size_t name_len = ext - basename_start; + strncpy(output_file + dir_len, basename_start, name_len); + output_file[dir_len + name_len] = '\0'; + + // Replace last dot with underscore (for .qNN pattern) + char *last_dot = strrchr(output_file, '.'); + if (last_dot && last_dot > output_file + dir_len) { + *last_dot = '_'; + } + } else { + // No .tad extension, copy entire basename + strcpy(output_file + dir_len, basename_start); + } + + // Append appropriate extension + strcat(output_file, raw_pcm ? ".pcm" : ".wav"); + + if (verbose) { + printf("Auto-generated output path: %s\n", output_file); + } + } + if (verbose) { printf("%s\n", DECODER_VENDOR_STRING); printf("Input: %s\n", input_file); @@ -588,6 +672,11 @@ int main(int argc, char *argv[]) { return 1; } + // Write placeholder WAV header if not in raw PCM mode + if (!raw_pcm) { + write_wav_header(output, 0, TAD_CHANNELS, TAD_SAMPLE_RATE, 8); + } + // Decode chunks size_t offset = 0; size_t chunk_count = 0; @@ -629,13 +718,24 @@ int main(int argc, char *argv[]) { total_samples / (double)TAD_SAMPLE_RATE); } + // Update WAV header with correct size if not in raw PCM mode + if (!raw_pcm) { + uint32_t data_size = total_samples * TAD_CHANNELS; + fseek(output, 0, SEEK_SET); + write_wav_header(output, data_size, TAD_CHANNELS, TAD_SAMPLE_RATE, 8); + } + // Cleanup free(input_data); free(chunk_output); fclose(output); printf("Output written to: %s\n", output_file); - printf("Format: PCMu8 stereo @ %d Hz\n", TAD_SAMPLE_RATE); + if (raw_pcm) { + printf("Format: PCMu8 stereo @ %d Hz (raw PCM)\n", TAD_SAMPLE_RATE); + } else { + printf("Format: WAV file (8-bit unsigned PCM, stereo @ %d Hz)\n", TAD_SAMPLE_RATE); + } return 0; } diff --git a/video_encoder/encoder_tad.c b/video_encoder/encoder_tad.c index 6d0b42b..be6fdf8 100644 --- a/video_encoder/encoder_tad.c +++ b/video_encoder/encoder_tad.c @@ -22,16 +22,16 @@ static const float TAD32_COEFF_SCALARS[] = {64.0f, 45.255f, 32.0f, 22.627f, 16.0 // Linearly spaced from 1.0 (LL) to 2.0 (H9) // These weights are multiplied by quantiser_scale during quantization static const float BASE_QUANTISER_WEIGHTS[] = { - 1.0f, // LL (L9) - finest preservation - 1.111f, // H (L9) - 1.222f, // H (L8) - 1.333f, // H (L7) - 1.444f, // H (L6) - 1.556f, // H (L5) - 1.667f, // H (L4) - 1.778f, // H (L3) - 1.889f, // H (L2) - 2.0f // H (L1) - coarsest quantization + 1.0f, // LL (L9) - finest preservation + 1.0f, // H (L9) + 1.0f, // H (L8) + 1.0f, // H (L7) + 1.0f, // H (L6) + 1.1f, // H (L5) + 1.2f, // H (L4) + 1.3f, // H (L3) + 1.4f, // H (L2) + 1.5f // H (L1) - coarsest quantization }; // Forward declarations for internal functions @@ -223,9 +223,9 @@ static void compress_gamma(float *left, float *right, size_t count) { for (size_t i = 0; i < count; i++) { // encode(x) = sign(x) * |x|^γ where γ=0.5 float x = left[i]; - left[i] = signum(x) * sqrtf(fabsf(x)); + left[i] = signum(x) * powf(fabsf(x), 1.0f / 1.4142f); float y = right[i]; - right[i] = signum(y) * sqrtf(fabsf(y)); + right[i] = signum(y) * powf(fabsf(y), 1.0f / 1.4142f); } } diff --git a/video_encoder/encoder_tad_standalone.c b/video_encoder/encoder_tad_standalone.c index 335a052..8424c8e 100644 --- a/video_encoder/encoder_tad_standalone.c +++ b/video_encoder/encoder_tad_standalone.c @@ -44,10 +44,10 @@ static void generate_random_filename(char *filename) { //============================================================================= static void print_usage(const char *prog_name) { - printf("Usage: %s -i -o [options]\n", prog_name); + printf("Usage: %s -i [options]\n", prog_name); printf("Options:\n"); printf(" -i Input audio file (any format supported by FFmpeg)\n"); - printf(" -o Output TAD32 file\n"); + printf(" -o Output TAD32 file (optional, auto-generated as input.qN.tad)\n"); printf(" -q Quantization bits (default: 7, range: 4-8)\n"); printf(" Higher = more precision, larger files\n"); printf(" -s Quantiser scaling factor (default: 1.0, range: 0.5-4.0)\n"); @@ -119,12 +119,47 @@ int main(int argc, char *argv[]) { } } - if (!input_file || !output_file) { - fprintf(stderr, "Error: Input and output files are required\n"); + if (!input_file) { + fprintf(stderr, "Error: Input file is required\n"); print_usage(argv[0]); return 1; } + // Generate output filename if not provided + if (!output_file) { + // Allocate space for output filename + size_t input_len = strlen(input_file); + output_file = malloc(input_len + 32); // Extra space for .qNN.tad + + // Find the last directory separator + const char *basename_start = strrchr(input_file, '/'); + if (!basename_start) basename_start = strrchr(input_file, '\\'); + basename_start = basename_start ? basename_start + 1 : input_file; + + // Copy directory part + size_t dir_len = basename_start - input_file; + strncpy(output_file, input_file, dir_len); + + // Find the extension (last dot after basename) + const char *ext = strrchr(basename_start, '.'); + if (ext && ext > basename_start) { + // Copy basename without extension + size_t name_len = ext - basename_start; + strncpy(output_file + dir_len, basename_start, name_len); + output_file[dir_len + name_len] = '\0'; + } else { + // No extension, copy entire basename + strcpy(output_file + dir_len, basename_start); + } + + // Append .qNN.tad + sprintf(output_file + strlen(output_file), ".q%d.tad", max_index); + + if (verbose) { + printf("Auto-generated output path: %s\n", output_file); + } + } + if (verbose) { printf("%s\n", ENCODER_VENDOR_STRING); printf("Input: %s\n", input_file);