diff options
author | cbdev <cb@cbcdn.com> | 2021-04-20 20:40:18 +0200 |
---|---|---|
committer | cbdev <cb@cbcdn.com> | 2021-04-20 20:40:18 +0200 |
commit | efa395f65a88f1457a58a51bbde62c8c1f3c7588 (patch) | |
tree | 4ee2a85cecbff993121b137f13e9b82451b4dd75 | |
parent | d0a273724b2bb2ad775d0461fe8ee5de1e6bad2f (diff) | |
download | cswave-efa395f65a88f1457a58a51bbde62c8c1f3c7588.tar.gz cswave-efa395f65a88f1457a58a51bbde62c8c1f3c7588.tar.bz2 cswave-efa395f65a88f1457a58a51bbde62c8c1f3c7588.zip |
Make normalization optional, implement configurable delimiter
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | README.md | 9 | ||||
-rw-r--r-- | cswave.c | 83 |
3 files changed, 72 insertions, 22 deletions
@@ -1,4 +1,4 @@ -CFLAGS = -g -Wall -Wpedantic +CFLAGS = -Wall -Wpedantic all: cswave @@ -9,14 +9,16 @@ Any column of the input file can be used as sample data for a resulting monophon * `i16`: 16bit signed little-endian data * `i32`: 32bit signed little-endian data * `f32`: 32bit IEEE float data +* `nf32`: 32bit IEEE float data, but without the normalization step Note that the integer output data formats only use the integer part of the input data, ie. when using the `i32` output format, the input sample value `1.23` is truncated to the output sample value `1`. When using the integer output data formats, the exact sample value is preserved (as long as it fits the storage size). For the `f32` format, the -specification requires that the samples be normalized to the range `-1.0` to `1.0`, thus the sample values will not be preserved. +specification requires that the samples be normalized to the range `-1.0` to `1.0`, thus the sample values will not be preserved. Use the `nf32` +sample format when your input data is already normalized and you want to preserve the exact sample values. -Quoted text embedding the separation character `,` within CSV columns will confuse the CSV parser. Don't do that. +Quoted text embedding the separation character within CSV columns will confuse the CSV parser. Don't do that. ## Building @@ -29,7 +31,7 @@ To build the windows executable `cswave.exe`, the `mingw-w64` crosscompiler is r Call the tool using the invocation ``` -./cswave <file.csv> <file.wav> <column> <samplerate> [<format>] +./cswave <file.csv> <file.wav> <column> <samplerate> [<format>] [<delimiter>] ``` * `<file.csv>`: CSV input file @@ -37,3 +39,4 @@ Call the tool using the invocation * `<column>`: Zero-based column index within the CSV (e.g., `0`) * `<samplerate>`: Integer sample rate of the resulting output file (e.g., `44100`) * `<format>`: Output file sample format as listed above, default `i16` +* `<delimiter>`: Specify a single character to use as the delimiter within the CSV input, default `,` @@ -10,6 +10,8 @@ #include <inttypes.h> #include <math.h> +#define VERSION "0.2" + #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include <windows.h> @@ -78,7 +80,8 @@ typedef enum { fmt_i8, fmt_i16, fmt_i32, - fmt_f32 + fmt_f32, + fmt_nf32 } sample_format; typedef struct { @@ -117,12 +120,12 @@ typedef struct /*_chunk_data*/ { } hdr_data_t; #pragma pack(pop) -//TODO configurable csv delimiter - static int usage(char* fn){ - fprintf(stdout, "Call as %s <file.csv> <file.wav> <column> <samplerate> [<format>]\n", fn); - fprintf(stdout, "Supported wave formats: i8, i16 (default), i32, f32\n"); - fprintf(stdout, "Note that when using integer output formats, floating point input samples will be truncated to their integer parts\n"); + fprintf(stdout, "cswave " VERSION " - Convert CSV columns to WAVE files\n"); + fprintf(stdout, "\tUsage: %s <file.csv> <file.wav> <column> <samplerate> [<format>] [<delimiter>]\n\n", fn); + fprintf(stdout, "Supported wave formats: i8, i16 (default), i32, f32, nf32\n"); + fprintf(stdout, "When using integer output formats, floating point input samples will be truncated to their integer parts.\n"); + fprintf(stdout, "The nf32 format will store samples as float32 values without normalization.\n"); return EXIT_FAILURE; } @@ -143,6 +146,10 @@ static sample_format sampleformat_parse(char* fmt){ return fmt_f32; } + else if(!strcmp(fmt, "nf32")){ + return fmt_nf32; + } + return fmt_i16; } @@ -154,6 +161,7 @@ static size_t sampleformat_bits(sample_format fmt){ return 16; case fmt_i32: case fmt_f32: + case fmt_nf32: return 32; } @@ -176,12 +184,15 @@ static void push_sample(sample_t sample, sample_format fmt, int fd){ write(fd, &i32, 4); break; case fmt_f32: - write(fd, &sample.f32, 4); + case fmt_nf32: + if(write(fd, &sample.f32, 4) != 4){ + fprintf(stderr, "Failed to write\n"); + } break; } } -static size_t process(FILE* src, size_t column, sample_format fmt, int dst){ +static size_t process(FILE* src, size_t column, sample_format fmt, int dst, char delimiter){ char* line, *value; size_t offset = 0; size_t bytes_alloc = 0, samples = 0; @@ -195,7 +206,7 @@ static size_t process(FILE* src, size_t column, sample_format fmt, int dst){ break; } - if(*value == ','){ + if(*value == delimiter){ offset++; } } @@ -208,6 +219,7 @@ static size_t process(FILE* src, size_t column, sample_format fmt, int dst){ sample.i64 = strtoll(value, NULL, 0); sample.f32 = strtof(value, NULL); + //fprintf(stderr, "Original sample %d, value %f\n", samples, sample.f32); push_sample(sample, fmt, dst); samples++; @@ -219,16 +231,31 @@ static size_t process(FILE* src, size_t column, sample_format fmt, int dst){ static float* float_reference(int fd, size_t samples){ float* data = calloc(samples, sizeof(float)), max = 0; - size_t n = 0; + size_t n = 0, nmax = 0; + ssize_t bytes = 0, current_read = 0; + + //read entire file + for(current_read = read(fd, data, samples * sizeof(float)); bytes != samples * sizeof(float); current_read = read(fd, ((uint8_t*)(data)) + bytes, (samples * sizeof(float)) - bytes)){ + if(current_read <= 0){ + fprintf(stderr, "Failed to read back raw data\n"); + free(data); + return NULL; + } + + bytes += current_read; + fprintf(stdout, "Readback progress %" PRIsize_t " of %" PRIsize_t " bytes\n", bytes, samples * sizeof(float)); + } + for(n = 0; n < samples; n++){ - read(fd, data + n, 4); + //fprintf(stderr, "Readback sample %d, value %f\n", n, data[n]); if(fabsf(data[n]) > fabsf(max)){ max = data[n]; + nmax = n; } } - fprintf(stdout, "Determined maximum sample value as %f\n", max); + fprintf(stdout, "Determined maximum sample value as %f (sample %" PRIsize_t ")\n", max, nmax); for(n = 0; n < samples; n++){ data[n] /= fabsf(max); @@ -280,11 +307,23 @@ static void write_headers(int fd, sample_format fmt, size_t samples, size_t samp } int main(int argc, char** argv){ + int normalize = 1; + char delimiter = ','; + ssize_t bytes_written = 0, current = 0; + if(argc < 5){ return usage(argv[0]); } + if(argc == 7){ + delimiter = argv[6][0]; + } + sample_format fmt = sampleformat_parse(argv[5]); + if(fmt == fmt_nf32){ + fmt = fmt_f32; + normalize = 0; + } FILE* in = fopen(argv[1], "r"); if(!in){ @@ -300,22 +339,30 @@ int main(int argc, char** argv){ } write_headers(output_fd, fmt, 0, strtoul(argv[4], NULL, 10)); - size_t samples = process(in, strtoul(argv[3], NULL, 10), fmt, output_fd); + size_t samples = process(in, strtoul(argv[3], NULL, 10), fmt, output_fd, delimiter); fclose(in); fprintf(stdout, "Finalizing output file (%" PRIsize_t " samples)\n", samples); lseek(output_fd, 0, SEEK_SET); write_headers(output_fd, fmt, samples, strtoul(argv[4], NULL, 10)); - + //floating point pcm needs to be normalized... - if(fmt == fmt_f32){ + if(fmt == fmt_f32 && normalize){ float* normalized = float_reference(output_fd, samples); + if(!normalized){ + close(output_fd); + return EXIT_FAILURE; + } + lseek(output_fd, 0, SEEK_SET); write_headers(output_fd, fmt, samples, strtoul(argv[4], NULL, 10)); - for(size_t n = 0; n < samples; n++){ - write(output_fd, normalized + n, 4); - //fprintf(stdout, "Normalized sample %f\n", normalized[n]); + + for(current = write(output_fd, normalized, samples * sizeof(float)); + bytes_written < samples * sizeof(float); + current = write(output_fd, ((uint8_t*) normalized) + bytes_written, (samples * sizeof(float) - bytes_written))){ + bytes_written += current; + fprintf(stdout, "Normalized write progress %" PRIsize_t " of %" PRIsize_t " bytes\n", bytes_written, samples * sizeof(float)); } free(normalized); } |