From 1a5f7b4f013e7294d40cd8539ae6d9e16ddf423c Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 24 Sep 2023 13:14:53 +0200 Subject: Initial version for unpacking 16bit little-endian multitrack WAV --- LICENSE.txt | 22 ++++++++ Makefile | 14 +++++ wavextract.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 LICENSE.txt create mode 100644 Makefile create mode 100644 wavextract.c diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..5827d3c --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2021, cbdev/FJS +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3c88a9d --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +CFLAGS = -Wall -Wpedantic + +all: wavextract + +wavextract: wavextract.c + +wavextract.exe: export CC = x86_64-w64-mingw32-gcc +wavextract.exe: wavextract.c + $(CC) $(CFLAGS) $(LDFLAGS) $< $(OBJS) $(LDLIBS) -o $@ + +clean: + $(RM) *.o + $(RM) wavextract + $(RM) wavextract.exe diff --git a/wavextract.c b/wavextract.c new file mode 100644 index 0000000..4192f64 --- /dev/null +++ b/wavextract.c @@ -0,0 +1,180 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VERSION "0.1" + +#ifdef _WIN32 + #define WIN32_LEAN_AND_MEAN + #include + #define PRIsize_t "Iu" + #define htole16(x) (x) + #define htole32(x) (x) +#else + #define PRIsize_t "lu" +#endif + +#pragma pack(push, 1) +typedef struct /*_chunk_riff*/ { + char magic_riff[4]; + uint32_t size; + char magic_wave[4]; +} hdr_riff_t; + +typedef struct /*_chunk_fmt_base*/ { + char magic[4]; + uint32_t size; + uint16_t fmt; + uint16_t channels; + uint32_t sample_rate; + uint32_t byte_rate; //(samplerate * sampleBits * channels) / 8. + uint16_t bitdepth; //(sampleBits * channels) / 8 + uint16_t samplebits; + //uint16_t extsize; //only present if fmt != 1 +} hdr_fmt_t; + +typedef struct /*_chunk_fact*/ { + char magic[4]; + uint32_t size; + uint32_t samples; +} hdr_fact_t; + +typedef struct /*_chunk_data*/ { + char magic[4]; + uint32_t size; +} hdr_data_t; +#pragma pack(pop) + +static int usage(char* fn){ + fprintf(stdout, "wavextract " VERSION " - Convert WAVE files to CSV sample data\n"); + fprintf(stdout, "\tUsage: %s \n\n", fn); + //fprintf(stdout, "Supported wave formats: i8, i16 (default), i32, f32, nf32\n"); + return EXIT_FAILURE; +} + +int main(int argc, char** argv){ + size_t n; + #ifdef _WIN32 + _fmode = _O_BINARY; + #endif + + if(argc < 2){ + return usage(argv[0]); + } + + fprintf(stderr, "Opening wave input file %s\n", argv[1]); + int source_fd = open(argv[1], O_RDONLY); + if(source_fd < 0){ + fprintf(stderr, "Failed to open input file %s: %s\n", argv[1], strerror(errno)); + return EXIT_FAILURE; + } + + hdr_riff_t riff; + if(read(source_fd, &riff, sizeof(riff)) != sizeof(riff)){ + close(source_fd); + fprintf(stderr, "Failed to read input data: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + + if(memcmp(riff.magic_riff, "RIFF", 4) || memcmp(riff.magic_wave, "WAVE", 4)){ + close(source_fd); + fprintf(stderr, "Invalid RIFF/WAVE magic\n"); + return EXIT_FAILURE; + } + + fprintf(stderr, "Chunk size: %d\n", riff.size); + + hdr_fmt_t fmt; + uint16_t extra_data; + if(read(source_fd, &fmt, sizeof(fmt)) != sizeof(fmt)){ + close(source_fd); + fprintf(stderr, "Failed to read input data: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + + if(memcmp(fmt.magic, "fmt ", 4)){ + close(source_fd); + fprintf(stderr, "Invalid format magic\n"); + return EXIT_FAILURE; + } + + fprintf(stderr, "Format chunk size: %d\n", fmt.size); + fprintf(stderr, "Format: %d\n", fmt.fmt); + fprintf(stderr, "Channels: %d\n", fmt.channels); + fprintf(stderr, "Samplerate: %d\n", fmt.sample_rate); + fprintf(stderr, "Byterate: %d\n", fmt.byte_rate); + fprintf(stderr, "Align: %d\n", fmt.bitdepth); + fprintf(stderr, "Samplebits: %d\n", fmt.samplebits); + + if(fmt.fmt != 1){ + read(source_fd, &extra_data, 2); + } + + if(fmt.fmt == 1 && fmt.size > 16){ + fprintf(stderr, "Skipping %d additional header bytes\n", fmt.size - 16); + for(n = 16; n < fmt.size; n++){ + read(source_fd, &extra_data, 1); + } + } + + /* For now, just skip further analysis and bail on incompatible format */ + if(fmt.samplebits != 16){ + close(source_fd); + fprintf(stderr, "This format is currently not supported\n"); + return EXIT_FAILURE; + } + + hdr_data_t data_header; + if(read(source_fd, &data_header, sizeof(data_header)) != sizeof(data_header)){ + close(source_fd); + fprintf(stderr, "Failed to read input data: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + + if(memcmp(data_header.magic, "data", 4)){ + close(source_fd); + fprintf(stderr, "Invalid data magic\n"); + return EXIT_FAILURE; + } + + fprintf(stderr, "%d bytes of sample data in file\n", data_header.size); + + uint16_t sample; + uint8_t channel = 0; + + for(n = 0; n < data_header.size / 2; n++){ + //read sample + if(read(source_fd, &sample, 2) != 2){ + close(source_fd); + fprintf(stderr, "Failed to read input data at sample %ld: %s\n", n, strerror(errno)); + return EXIT_FAILURE; + } + + if(channel == 0){ + printf("%ld,%d", n, (int16_t) sample); + } + else{ + printf(",%d", (int16_t) sample); + } + + if(channel == fmt.channels - 1){ + printf("\n"); + } + + channel += 1; + channel %= fmt.channels; + } + + close(source_fd); + fprintf(stderr, "All done\n"); + return EXIT_SUCCESS; +} -- cgit v1.2.3