From 546a2df45a94a37218663419faa8325dc5a010a1 Mon Sep 17 00:00:00 2001 From: chee Date: Thu, 18 Mar 2021 17:41:04 +0000 Subject: [PATCH] add wav2sketch.c by paul --- bleepbloopmachine/.gitignore | 1 + bleepbloopmachine/wav2sketch | Bin 0 -> 21808 bytes code/lib/__pycache__/freq.cpython-39.pyc | Bin 2037 -> 2037 bytes code/lib/freq.py | 235 +++++++------- wav2sketch.c | 381 +++++++++++++++++++++++ 5 files changed, 509 insertions(+), 108 deletions(-) create mode 100755 bleepbloopmachine/wav2sketch create mode 100644 wav2sketch.c diff --git a/bleepbloopmachine/.gitignore b/bleepbloopmachine/.gitignore index 567609b..26f9a46 100644 --- a/bleepbloopmachine/.gitignore +++ b/bleepbloopmachine/.gitignore @@ -1 +1,2 @@ build/ +./wav2sketch diff --git a/bleepbloopmachine/wav2sketch b/bleepbloopmachine/wav2sketch new file mode 100755 index 0000000000000000000000000000000000000000..80b0e10458c2e01bd9b83cd48f43306ca9f2e64e GIT binary patch literal 21808 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 v{G< +#include +#include +#include +#include +#include +#include +#include +#include + +uint8_t ulaw_encode(int16_t audio); +void print_byte(FILE *out, uint8_t b); +void filename2samplename(void); +uint32_t padding(uint32_t length, uint32_t block); +uint8_t read_uint8(FILE *in); +int16_t read_int16(FILE *in); +uint32_t read_uint32(FILE *in); +void die(const char *format, ...) __attribute__((format(printf, 1, 2))); + +// WAV file format: +// http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html + +const char *filename = ""; +char samplename[64]; +unsigned int bcount, wcount; +unsigned int total_length = 0; +int pcm_mode = 0; + +void wav2c(FILE *in, FILE *out, FILE *outh) { + uint32_t header[4]; + int16_t format, channels, bits; + uint32_t rate; + uint32_t i, length, padlength = 0, arraylen; + uint32_t chunkSize; + int32_t audio = 0; + + // read the WAV file's header + for (i = 0; i < 4; i++) { + header[i] = read_uint32(in); + } + while (header[3] != 0x20746D66) { + // skip past unknown sections until "fmt " + chunkSize = read_uint32(in); + for (i = 0; i < chunkSize; i++) { + read_uint8(in); + } + header[3] = read_uint32(in); + } + chunkSize = read_uint32(in); + + // read the audio format parameters + format = read_int16(in); + channels = read_int16(in); + rate = read_uint32(in); + read_uint32(in); // ignore byterate + read_int16(in); // ignore blockalign + bits = read_int16(in); + // printf("format: %d, channels: %d, rate: %d, bits %d\n", format, channels, + // rate, bits); + if (format != 1) + die("file %s is compressed, only uncompressed supported", filename); + if (rate != 44100 && rate != 22050 && rate != 11025 /*&& rate != 8000*/) + die("sample rate %d in %s is unsupported\n" + "Only 44100, 22050, 11025 work", + rate, filename); + if (channels != 1 && channels != 2) + die("file %s has %d channels, but only 1 & 2 are supported", filename, + channels); + if (bits != 16) + die("file %s has %d bit format, but only 16 is supported", filename, bits); + + // skip past any extra data on the WAVE header (hopefully it doesn't matter?) + for (chunkSize -= 16; chunkSize > 0; chunkSize--) { + read_uint8(in); + } + + // read the data header, skip non-audio data + while (1) { + header[0] = read_uint32(in); + length = read_uint32(in); + if (header[0] == 0x61746164) + break; // beginning of actual audio data + // skip over non-audio data + for (i = 0; i < length; i++) { + read_uint8(in); + } + } + + // the length must be a multiple of the data size + if (channels == 2) { + if (length % 4) + die("file %s data length is not a multiple of 4", filename); + length = length / 4; + } + if (channels == 1) { + if (length % 1) + die("file %s data length is not a multiple of 2", filename); + length = length / 2; + } + if (length > 0xFFFFFF) + die("file %s data length is too long", filename); + bcount = 0; + + // AudioPlayMemory requires padding to 2.9 ms boundary (128 samples @ 44100) + if (rate == 44100) { + padlength = padding(length, 128); + format = 1; + } else if (rate == 22050) { + padlength = padding(length, 64); + format = 2; + } else if (rate == 11025) { + padlength = padding(length, 32); + format = 3; + } + if (pcm_mode) { + arraylen = ((length + padlength) * 2 + 3) / 4 + 1; + format |= 0x80; + } else { + arraylen = (length + padlength + 3) / 4 + 1; + } + total_length += arraylen; + + // output a minimal header, just the length, #bits and sample rate + fprintf(outh, "extern const unsigned int AudioSample%s[%d];\n", samplename, + arraylen); + fprintf(out, "// Converted from %s, using %d Hz, %s encoding\n", filename, + rate, (pcm_mode ? "16 bit PCM" : "u-law")); + fprintf(out, "PROGMEM const unsigned int AudioSample%s[%d] = {\n", samplename, + arraylen); + fprintf(out, "0x%08X,", length | (format << 24)); + wcount = 1; + + // finally, read the audio data + while (length > 0) { + if (channels == 1) { + audio = read_int16(in); + } else { + audio = read_int16(in); + audio += read_int16(in); + audio /= 2; + } + if (pcm_mode) { + print_byte(out, audio); + print_byte(out, audio >> 8); + } else { + print_byte(out, ulaw_encode(audio)); + } + length--; + } + while (padlength > 0) { + print_byte(out, 0); + padlength--; + } + while (bcount > 0) { + print_byte(out, 0); + } + if (wcount > 0) + fprintf(out, "\n"); + fprintf(out, "};\n"); +} + +uint8_t ulaw_encode(int16_t audio) { + uint32_t mag, neg; + + // http://en.wikipedia.org/wiki/G.711 + if (audio >= 0) { + mag = audio; + neg = 0; + } else { + mag = audio * -1; + neg = 0x80; + } + mag += 128; + if (mag > 0x7FFF) + mag = 0x7FFF; + if (mag >= 0x4000) + return neg | 0x70 | ((mag >> 10) & 0x0F); // 01wx yz00 0000 0000 + if (mag >= 0x2000) + return neg | 0x60 | ((mag >> 9) & 0x0F); // 001w xyz0 0000 0000 + if (mag >= 0x1000) + return neg | 0x50 | ((mag >> 8) & 0x0F); // 0001 wxyz 0000 0000 + if (mag >= 0x0800) + return neg | 0x40 | ((mag >> 7) & 0x0F); // 0000 1wxy z000 0000 + if (mag >= 0x0400) + return neg | 0x30 | ((mag >> 6) & 0x0F); // 0000 01wx yz00 0000 + if (mag >= 0x0200) + return neg | 0x20 | ((mag >> 5) & 0x0F); // 0000 001w xyz0 0000 + if (mag >= 0x0100) + return neg | 0x10 | ((mag >> 4) & 0x0F); // 0000 0001 wxyz 0000 + return neg | 0x00 | ((mag >> 3) & 0x0F); // 0000 0000 1wxy z000 +} + +// compute the extra padding needed +uint32_t padding(uint32_t length, uint32_t block) { + uint32_t extra; + + extra = length % block; + if (extra == 0) + return 0; + return block - extra; +} + +// pack the output bytes into 32 bit words, lsb first, and +// format the data nicely with commas and newlines +void print_byte(FILE *out, uint8_t b) { + static uint32_t buf32 = 0; + + buf32 |= (b << (8 * bcount++)); + if (bcount >= 4) { + fprintf(out, "0x%08X,", buf32); + buf32 = 0; + bcount = 0; + if (++wcount >= 8) { + fprintf(out, "\n"); + wcount = 0; + } + } +} + +// convert the WAV filename into a C-compatible name +void filename2samplename(void) { + int len, i, n; + char c; + + len = strlen(filename) - 4; + if (len >= sizeof(samplename) - 1) + len = sizeof(samplename) - 1; + for (i = 0, n = 0; n < len; i++) { + c = filename[i]; + if (isalpha(c) || c == '_' || (isdigit(c) && n > 0)) { + samplename[n] = (n == 0) ? toupper(c) : tolower(c); + n++; + } + } + samplename[n] = 0; +} + +const char *title = "// Audio data converted from WAV file by wav2sketch\n\n"; + +int main(int argc, char **argv) { + DIR *dir; + struct dirent *f; + struct stat s; + FILE *fp, *outc = NULL, *outh = NULL; + char buf[128]; + int i, len; + + // By default, audio is u-law encoded to reduce the memory requirement + // in half. However, u-law does add distortion. If "-16" is specified + // on the command line, the original 16 bit PCM samples are used. + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-16") == 0) + pcm_mode = 1; + } + dir = opendir("."); + if (!dir) + die("unable to open directory"); + while (1) { + f = readdir(dir); + if (!f) + break; + // if ((f->d_type & DT_DIR)) continue; // skip directories + // if (!(f->d_type & DT_REG)) continue; // skip special files + if (stat(f->d_name, &s) < 0) + continue; // skip if unable to stat + if (S_ISDIR(s.st_mode)) + continue; // skip directories + if (!S_ISREG(s.st_mode)) + continue; // skip special files + filename = f->d_name; + len = strlen(filename); + if (len < 5) + continue; + if (strcasecmp(filename + len - 4, ".wav") != 0) + continue; + fp = fopen(filename, "rb"); + if (!fp) + die("unable to read file %s", filename); + filename2samplename(); + printf("converting: %s --> AudioSample%s\n", filename, samplename); + snprintf(buf, sizeof(buf), "AudioSample%s.cpp", samplename); + outc = fopen(buf, "w"); + if (outc == NULL) + die("unable to write %s", buf); + snprintf(buf, sizeof(buf), "AudioSample%s.h", samplename); + outh = fopen(buf, "w"); + if (outh == NULL) + die("unable to write %s\n", buf); + fprintf(outh, "%s", title); + fprintf(outc, "%s", title); + fprintf(outc, "#include \n"); + fprintf(outc, "#include \"%s\"\n\n", buf); + wav2c(fp, outc, outh); + // wav2c(fp, stdout, stdout); + fclose(outc); + fclose(outh); + fclose(fp); + } + printf("Total data size %d bytes\n", total_length * 4); + return 0; +} + +uint8_t read_uint8(FILE *in) { + int c1; + + c1 = fgetc(in); + if (c1 == EOF) + die("error, end of data while reading from %s\n", filename); + c1 &= 255; + return c1; +} + +int16_t read_int16(FILE *in) { + int c1, c2; + + c1 = fgetc(in); + if (c1 == EOF) + die("error, end of data while reading from %s\n", filename); + c2 = fgetc(in); + if (c2 == EOF) + die("error, end of data while reading from %s\n", filename); + c1 &= 255; + c2 &= 255; + return (c2 << 8) | c1; +} + +uint32_t read_uint32(FILE *in) { + int c1, c2, c3, c4; + + c1 = fgetc(in); + if (c1 == EOF) + die("error, end of data while reading from %s\n", filename); + c2 = fgetc(in); + if (c2 == EOF) + die("error, end of data while reading from %s\n", filename); + c3 = fgetc(in); + if (c3 == EOF) + die("error, end of data while reading from %s\n", filename); + c4 = fgetc(in); + if (c4 == EOF) + die("error, end of data while reading from %s\n", filename); + c1 &= 255; + c2 &= 255; + c3 &= 255; + c4 &= 255; + return (c4 << 24) | (c3 << 16) | (c2 << 8) | c1; +} + +void die(const char *format, ...) { + va_list args; + va_start(args, format); + fprintf(stderr, "wav2sketch: "); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + exit(1); +}