Last year I was trying to play music while running Chris' uncanny eyes on a T4.1 and gave up because the sound was stuttery. I probably went wrong in not paying more attention to advice I got from smarter people here.
This year I thought I'd revisit it and try Teensy threads but as I read new stuff and reread from last year and found things happening that made me think I was actually right on the edge of stack corruption, I tried something different, which is basically working.
I'm working mostly on a mac, sometimes use a raspberry pi for some processing.
I used commands like these in terminal windows to create wav files:
I think those create mono (-ac 1) reduced bitrate (-ab 22050) time limited wav files.
At first I ran wav2sketch on those but then I realized that wav2sketch couldn't quite work with files that fill EXTMEM--it can put files in PROGMEM which will have less than 8megs available and EXTMEM could be 16 megs.
So I modified that to be wav2bin:
and wrote a little arduino program to go with that:
It all seems to work and the sound is much better in the version where I included that with uncanny eyes. The first 32 bit word of the .bin file (and the way playmem works) is the encoding in the top byte and 24 bits of length. So basically the same limit as if you add 16megs of PSRAM to a T4.1 (I've just used 8 in my proof of concept testing. I don't have very good hearing and I am just using a 1" x 4" speaker with a MAX98357, so the sound might not hold up to critical listening.
This year I thought I'd revisit it and try Teensy threads but as I read new stuff and reread from last year and found things happening that made me think I was actually right on the edge of stack corruption, I tried something different, which is basically working.
I'm working mostly on a mac, sometimes use a raspberry pi for some processing.
I used commands like these in terminal windows to create wav files:
Code:
for f in *.m4a; do ffmpeg -i "$f" -ac 1 -ab 22050 -ss 0:00:01 -t 0:01:30 "${f/%m4a/wav}"; done
for f in *.mp3; do ffmpeg -i "$f" -ac 1 -ab 22050 -ss 0:00:01 -t 0:01:30 "${f/%mp3/wav}"; done
I think those create mono (-ac 1) reduced bitrate (-ab 22050) time limited wav files.
At first I ran wav2sketch on those but then I realized that wav2sketch couldn't quite work with files that fill EXTMEM--it can put files in PROGMEM which will have less than 8megs available and EXTMEM could be 16 megs.
So I modified that to be wav2bin:
Code:
// 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
// //most of must be wrong on arm i686-w64-mingw32-gcc -s -O2 -Wall wav2sketch.c -o wav2sketch.exe
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
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)));
static uint32_t buf32=0;
// 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) //we're not gonna need 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); //so chunksize is in the wav file
for (i=0; i < chunkSize; i++) {
read_uint8(in);
}
header[3] = read_uint32(in); //and then there's more header
}
chunkSize = read_uint32(in); //and a new chunksize
// read the audio format parameters
format = read_int16(in); // now we're getting audio info we'll need
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); //printed comment seemed to say I had this bitrate
format = 1;
} else if (rate == 22050) {
padlength = padding(length, 64); //This is the one I'm gonna use--format 2
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; //again this is me
}
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); //this is going to the header file
fprintf(out, "// Converted from %s, using %d Hz, %s encoding\n", filename, rate,
(pcm_mode ? "16 bit PCM" : "u-law")); //and this to the .cpp file
fprintf(out, "PROGMEM const unsigned int AudioSample%s[%d] = {\n", samplename, arraylen); //note arraylen
fprintf(out, "0x%08X,", length | (format << 24)); //here's the first 32 bit word--I see 0x013C8FE8,
//which is the length in bytes in lower 3 bytes and format in upper byte.
//so max length would be 16 777 000 bytes, which is great!
*/
buf32 = length | (format << 24); //so just send the same thing to .bin file
fwrite(&buf32,sizeof(buf32),1,out);
wcount = 1;
// finally, read the audio data
while (length > 0) {
if (channels == 1) {
audio = read_int16(in); //one channel--this is me
} 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)); //this is me so print_byte must be sending ascii hex to the file
}
length--;
}
while (padlength > 0) {
print_byte(out, 0);
padlength--;
}
while (bcount > 0) {
print_byte(out, 0);
}
/* this is formatting lines on the text file--we don't want it
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
else 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)
{
buf32 |= (b << (8 * bcount++)); //we do need this packing stuff
if (bcount >= 4) {
//fprintf(out, "0x%08X,", buf32); //this prints alpha--we want just send 32 bit word to .bin
fwrite(&buf32,sizeof(buf32),1,out);
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"); //open the wav file
if (!fp) die("unable to read file %s", filename);
filename2samplename();
printf("converting: %s --> AudioSample%s\n", filename, samplename);
snprintf(buf, sizeof(buf), "Ext%s.bin", samplename); //puts the new filename into buf
outc = fopen(buf, "wb"); //here's where we open the output file we built the name in buf
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 <Arduino.h>\n"); //more header stuff he added later
//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);
}
and wrote a little arduino program to go with that:
Code:
#include <SD.h>
#include <Audio.h>
//ExFatFile root;
ExFatFile song;
EXTMEM unsigned int extSong[2097151]; //8 megabytes
// GUItool: begin automatically generated code
#include <Wire.h>
AudioSynthWaveformDc dc1; //xy=1527.381031036377,743.0953025817871
AudioPlayMemory playMem1; //xy=1541.6666564941406,567.3809337615967
AudioEffectMultiply multiply1; //xy=1853.095272064209,685.952428817749
AudioOutputI2S i2s1; //xy=2034.5238167898995,688.8095310756138
AudioConnection patchCord1(dc1, 0, multiply1, 1);
AudioConnection patchCord2(playMem1, 0, multiply1, 0);
AudioConnection patchCord3(multiply1, 0, i2s1, 0);
// GUItool: end automatically generated code
// GUItool: end automatically generated code
void setup(void)
{
Serial.begin(115200);
//while (!Serial.available() ) {};
while (!SD.begin(BUILTIN_SDCARD))
{
Serial.println("...waiting for SD card...");
delay(250);
}
Serial.printf("\n");
Serial.printf("Before [0],[1],[280060],[280061] %x , %x , %x ,%x \n",extSong[0],extSong[1],extSong[280060],extSong[280601]); size_t start=millis();
song.open("ExtCelestialsodapop.bin",FILE_READ);
if (!song) Serial.print("Song not found\n");
size_t length = song.fileSize();
Serial.printf("Song length is %d\n",length);
song.read(extSong,length);
song.close();
Serial.printf("Finished disk read to ExtMem. It took %d millis\n",millis()-start);
Serial.printf("After [0],[1],[280060],[280061] %x , % x, %x ,%x \n",extSong[0],extSong[1],extSong[280060],extSong[280061]);
Serial.flush();
AudioMemory(40);
playMem1.play(extSong);
}
void loop() {
// put your main code here, to run repeatedly:
dc1.amplitude(analogRead(A0/1030));
if (!playMem1.isPlaying()) playMem1.play(extSong);
}
It all seems to work and the sound is much better in the version where I included that with uncanny eyes. The first 32 bit word of the .bin file (and the way playmem works) is the encoding in the top byte and 24 bits of length. So basically the same limit as if you add 16megs of PSRAM to a T4.1 (I've just used 8 in my proof of concept testing. I don't have very good hearing and I am just using a 1" x 4" speaker with a MAX98357, so the sound might not hold up to critical listening.