aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--README.md9
-rw-r--r--cswave.c83
3 files changed, 72 insertions, 22 deletions
diff --git a/Makefile b/Makefile
index 51fafd4..e00aa80 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-CFLAGS = -g -Wall -Wpedantic
+CFLAGS = -Wall -Wpedantic
all: cswave
diff --git a/README.md b/README.md
index caf4f3e..9ba5152 100644
--- a/README.md
+++ b/README.md
@@ -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 `,`
diff --git a/cswave.c b/cswave.c
index c26000b..0a7d70a 100644
--- a/cswave.c
+++ b/cswave.c
@@ -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);
}