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 0000000..80b0e10 Binary files /dev/null and b/bleepbloopmachine/wav2sketch differ diff --git a/code/lib/__pycache__/freq.cpython-39.pyc b/code/lib/__pycache__/freq.cpython-39.pyc index f66c5a5..867e886 100644 Binary files a/code/lib/__pycache__/freq.cpython-39.pyc and b/code/lib/__pycache__/freq.cpython-39.pyc differ diff --git a/code/lib/freq.py b/code/lib/freq.py index d2e7ed4..d97e441 100644 --- a/code/lib/freq.py +++ b/code/lib/freq.py @@ -1,110 +1,129 @@ + freq = { - 'b8': 7902.133, - 'a#8': 7458.620, - 'a8': 7040.000, - 'g#8': 6644.875, - 'g8': 6271.927, - 'f#8': 5919.911, - 'f8': 5587.652, - 'e8': 5274.041, - 'd#8': 4978.032, - 'd8': 4698.636, - 'c#8': 4434.922, - 'c8': 4186.009, - 'b7': 3951.066, - 'a#7': 3729.310, - 'a7': 3520.000, - 'g#7': 3322.438, - 'g7': 3135.963, - 'f#7': 2959.955, - 'f7': 2793.826, - 'e7': 2637.020, - 'd#7': 2489.016, - 'd7': 2349.318, - 'c#7': 2217.461, - 'c7': 2093.005, - 'b6': 1975.533, - 'a#6': 1864.655, - 'a6': 1760.000, - 'g#6': 1661.219, - 'g6': 1567.982, - 'f#6': 1479.978, - 'f6': 1396.913, - 'e6': 1318.510, - 'd#6': 1244.508, - 'd6': 1174.659, - 'c#6': 1108.731, - 'c6': 1046.502, - 'b5': 987.7666, - 'a#5': 932.3275, - 'a5': 880.0000, - 'g#5': 830.6094, - 'g5': 783.9909, - 'f#5': 739.9888, - 'f5': 698.4565, - 'e5': 659.2551, - 'd#5': 622.2540, - 'd5': 587.3295, - 'c#5': 554.3653, - 'c5': 523.2511, - 'b4': 493.8833, - 'a#4': 466.1638, - 'a4': 440.0000, - 'g#4': 415.3047, - 'g4': 391.9954, - 'f#4': 369.9944, - 'f4': 349.2282, - 'e4': 329.6276, - 'd#4': 311.1270, - 'd4': 293.6648, - 'c#4': 277.1826, - 'c4': 261.6256, - 'b3': 246.9417, - 'a#3': 233.0819, - 'a3': 220.0000, - 'g#3': 207.6523, - 'g3': 195.9977, - 'f#3': 184.9972, - 'f3': 174.6141, - 'e3': 164.8138, - 'd#3': 155.5635, - 'd3': 146.8324, - 'c#3': 138.5913, - 'c3': 130.8128, - 'b2': 123.4708, - 'a#2': 116.5409, - 'a2': 110.0000, - 'g#2': 103.8262, - 'g2': 97.99886, - 'f#2': 92.49861, - 'f2': 87.30706, - 'e2': 82.40689, - 'd#2': 77.78175, - 'd2': 73.41619, - 'c#2': 69.29566, - 'c2': 65.40639, - 'b1': 61.73541, - 'a#1': 58.27047, - 'a1': 55.00000, - 'g#1': 51.91309, - 'g1': 48.99943, - 'f#1': 46.24930, - 'f1': 43.65353, - 'e1': 41.20344, - 'd#1': 38.89087, - 'd1': 36.70810, - 'c#1': 34.64783, - 'c1': 32.70320, - 'b0': 30.86771, - 'a#0': 29.13524, - 'a0': 27.50000, - 'g#0': 25.95654, - 'g0': 24.49971, - 'f#0': 23.12465, - 'f0': 21.82676, - 'e0': 20.60172, - 'd#0': 19.44544, - 'd0': 18.35405, - 'c#0': 17.32391, - 'c0': 16.35160, + 'b8': 7902.133, + 'a#8': 7458.620, + 'a8': 7040.000, + 'g#8': 6644.875, + 'g8': 6271.927, + 'f#8': 5919.911, + 'f8': 5587.652, + 'e8': 5274.041, + 'd#8': 4978.032, + 'd8': 4698.636, + 'c#8': 4434.922, + 'c8': 4186.009, + 'b7': 3951.066, + 'a#7': 3729.310, + 'a7': 3520.000, + 'g#7': 3322.438, + 'g7': 3135.963, + 'f#7': 2959.955, + 'f7': 2793.826, + 'e7': 2637.020, + 'd#7': 2489.016, + 'd7': 2349.318, + 'c#7': 2217.461, + 'c7': 2093.005, + 'b6': 1975.533, + 'a#6': 1864.655, + 'a6': 1760.000, + 'g#6': 1661.219, + 'g6': 1567.982, + 'f#6': 1479.978, + 'f6': 1396.913, + 'e6': 1318.510, + 'd#6': 1244.508, + 'd6': 1174.659, + 'c#6': 1108.731, + 'c6': 1046.502, + 'b5': 987.7666, + 'a#5': 932.3275, + 'a5': 880.0000, + 'g#5': 830.6094, + 'g5': 783.9909, + 'f#5': 739.9888, + 'f5': 698.4565, + 'e5': 659.2551, + 'd#5': 622.2540, + 'd5': 587.3295, + 'c#5': 554.3653, + 'c5': 523.2511, + 'b4': 493.8833, + 'a#4': 466.1638, + 'a4': 440.0000, + 'g#4': 415.3047, + 'g4': 391.9954, + 'f#4': 369.9944, + 'f4': 349.2282, + 'e4': 329.6276, + 'd#4': 311.1270, + 'd4': 293.6648, + 'c#4': 277.1826, + 'c4': 261.6256, + 'b3': 246.9417, + 'a#3': 233.0819, + 'a3': 220.0000, + 'g#3': 207.6523, + 'g3': 195.9977, + 'f#3': 184.9972, + 'f3': 174.6141, + 'e3': 164.8138, + 'd#3': 155.5635, + 'd3': 146.8324, + 'c#3': 138.5913, + 'c3': 130.8128, + 'b2': 123.4708, + 'a#2': 116.5409, + 'a2': 110.0000, + 'g#2': 103.8262, + 'g2': 97.99886, + 'f#2': 92.49861, + 'f2': 87.30706, + 'e2': 82.40689, + 'd#2': 77.78175, + 'd2': 73.41619, + 'c#2': 69.29566, + 'c2': 65.40639, + 'b1': 61.73541, + 'a#1': 58.27047, + 'a1': 55.00000, + 'g#1': 51.91309, + 'g1': 48.99943, + 'f#1': 46.24930, + 'f1': 43.65353, + 'e1': 41.20344, + 'd#1': 38.89087, + 'd1': 36.70810, + 'c#1': 34.64783, + 'c1': 32.70320, + 'b0': 30.86771, + 'a#0': 29.13524, + 'a0': 27.50000, + 'g#0': 25.95654, + 'g0': 24.49971, + 'f#0': 23.12465, + 'f0': 21.82676, + 'e0': 20.60172, + 'd#0': 19.44544, + 'd0': 18.35405, + 'c#0': 17.32391, + 'c0': 16.35160, } + +cf = [[], [], [], [], [], [], [], [], [], [], [], []] + +notes = ["c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b"] + +for k, v in freq.items(): + note_name = k[0:-1] + octave = int(k[-1]) + cf[notes.index(note_name)].insert(0, v) + +print("auto freq = {") +for note in cf: + print("{", end="") + for octave in note: + print(octave, end=", ") + print("}", end=",") + +print("};") diff --git a/wav2sketch.c b/wav2sketch.c new file mode 100644 index 0000000..13e793d --- /dev/null +++ b/wav2sketch.c @@ -0,0 +1,381 @@ +// Convert a set of WAV audio files to C data arrays for the Teensy3 Audio +// Library Copyright 2014, Paul Stoffregen (paul@pjrc.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// compile with: gcc -O2 -Wall -o wav2sketch wav2sketch.c +// i686-w64-mingw32-gcc -s -O2 -Wall wav2sketch.c -o +// wav2sketch.exe + +#include +#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); +}