From fde836e36b1a1c01cc6615f85e89fda2047c98cb Mon Sep 17 00:00:00 2001 From: Ben Hitchcock Date: Sun, 2 Jun 2024 20:58:43 +0800 Subject: [PATCH] Adding ability to record and playback WAV files. --- Adafruit_VS1053.cpp | 67 +++++++ Adafruit_VS1053.h | 10 + examples/record_wav/record_wav.ino | 295 +++++++++++++++++++++++++++++ 3 files changed, 372 insertions(+) create mode 100644 examples/record_wav/record_wav.ino diff --git a/Adafruit_VS1053.cpp b/Adafruit_VS1053.cpp index f486789..760cdc1 100644 --- a/Adafruit_VS1053.cpp +++ b/Adafruit_VS1053.cpp @@ -619,6 +619,73 @@ void Adafruit_VS1053::startRecordOgg(boolean mic) { ; } +void Adafruit_VS1053::startRecordWav(boolean mic, uint16_t samplerate) { + softReset(); + while (!readyForData()) + ; + + sciWrite(VS1053_SCI_AICTRL0, samplerate); // Sample rate between 8000 and + // 48000 + sciWrite(VS1053_SCI_AICTRL1, 0); // Recording gain : 1024 : 1.If 0, use AGC + sciWrite(VS1053_SCI_AICTRL2, 4096); // Maximum AGC level: 1024 = 1. Only used + // if SCI_AICTRL1 is set to 0. + // Miscellaneous bits that also must be set before recording. + // sciWrite(VS1053_SCI_AICTRL3, 0); //joint stereo AGC + IMA ADPCM + sciWrite(VS1053_SCI_AICTRL3, 2); // LEFT only + // sciWrite(VS1053_SCI_AICTRL3, 3); //RIGHT only + // sciWrite(VS1053_SCI_AICTRL3, 4); //joint stereo AGC + LINEAR PCM + + while (!readyForData()) + ; + + if (mic) { + sciWrite(VS1053_REG_MODE, VS1053_MODE_SM_RESET | VS1053_MODE_SM_ADPCM | + VS1053_MODE_SM_SDINEW); + } else { + sciWrite(VS1053_REG_MODE, VS1053_MODE_SM_RESET | VS1053_MODE_SM_LINE1 | + VS1053_MODE_SM_ADPCM | VS1053_MODE_SM_SDINEW); + } + + while (!readyForData()) + ; + + // IMA fix patch + sciWrite(VS1053_REG_WRAMADDR, 0x8010); + sciWrite(VS1053_REG_WRAM, 0x3e12); + sciWrite(VS1053_REG_WRAM, 0xb817); + sciWrite(VS1053_REG_WRAM, 0x3e14); + sciWrite(VS1053_REG_WRAM, 0xf812); + sciWrite(VS1053_REG_WRAM, 0x3e01); + sciWrite(VS1053_REG_WRAM, 0xb811); + sciWrite(VS1053_REG_WRAM, 0x0007); + sciWrite(VS1053_REG_WRAM, 0x9717); + sciWrite(VS1053_REG_WRAM, 0x0020); + sciWrite(VS1053_REG_WRAM, 0xffd2); + sciWrite(VS1053_REG_WRAM, 0x0030); + sciWrite(VS1053_REG_WRAM, 0x11d1); + sciWrite(VS1053_REG_WRAM, 0x3111); + sciWrite(VS1053_REG_WRAM, 0x8024); + sciWrite(VS1053_REG_WRAM, 0x3704); + sciWrite(VS1053_REG_WRAM, 0xc024); + sciWrite(VS1053_REG_WRAM, 0x3b81); + sciWrite(VS1053_REG_WRAM, 0x8024); + sciWrite(VS1053_REG_WRAM, 0x3101); + sciWrite(VS1053_REG_WRAM, 0x8024); + sciWrite(VS1053_REG_WRAM, 0x3b81); + sciWrite(VS1053_REG_WRAM, 0x8024); + sciWrite(VS1053_REG_WRAM, 0x3f04); + sciWrite(VS1053_REG_WRAM, 0xc024); + sciWrite(VS1053_REG_WRAM, 0x2808); + sciWrite(VS1053_REG_WRAM, 0x4800); + sciWrite(VS1053_REG_WRAM, 0x36f1); + sciWrite(VS1053_REG_WRAM, 0x9811); + sciWrite(VS1053_REG_WRAMADDR, 0x8028); + sciWrite(VS1053_REG_WRAM, 0x2a00); + sciWrite(VS1053_REG_WRAM, 0x040e); +} + +void Adafruit_VS1053::stopRecordWav(void) { sciWrite(VS1053_SCI_AICTRL3, 1); } + void Adafruit_VS1053::GPIO_pinMode(uint8_t i, uint8_t dir) { if (i > 7) return; diff --git a/Adafruit_VS1053.h b/Adafruit_VS1053.h index 6a45d16..503bccf 100644 --- a/Adafruit_VS1053.h +++ b/Adafruit_VS1053.h @@ -255,6 +255,16 @@ class Adafruit_VS1053 { * @brief Stop the recording */ void stopRecordOgg(void); + /*! + * @brief Start recording + * @param mic mic=true for microphone input + * @param samplerate sample rate in hz (8000 - 48000) + */ + void startRecordWav(boolean mic, uint16_t samplerate); + /*! + * @brief Stop the recording + */ + void stopRecordWav(void); /*! * @brief Returns the number of words recorded * @return 2-byte unsigned int with the number of words diff --git a/examples/record_wav/record_wav.ino b/examples/record_wav/record_wav.ino new file mode 100644 index 0000000..5a0244d --- /dev/null +++ b/examples/record_wav/record_wav.ino @@ -0,0 +1,295 @@ +/*************************************************** + This is an example for the Adafruit VS1053 Codec Breakout + + Designed specifically to work with the Adafruit VS1053 Codec Breakout + ----> https://www.adafruit.com/products/1381 + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Based on code written by Limor Fried/Ladyada for Adafruit Industries. + Wav file recording by Ben Hitchcock + BSD license, all text above must be included in any redistribution + ****************************************************/ + +// This is a beta demo of Wav file recording. +// Connect a button between digital 7 on the Arduino and ground, +// press and hold to record. Once the button is lifted, the recording will play back. + +// A mic or line-in connection is required. See page 13 of the +// datasheet for wiring + +// include SPI, MP3 and SD libraries +#include +#include +#include + +// define the pins used +#define RESET 9 // VS1053 reset pin (output) +#define CS 10 // VS1053 chip select pin (output) +#define DCS 8 // VS1053 Data/command select pin (output) +#define CARDCS 4 // Card chip select pin +#define DREQ 3 // VS1053 Data request, ideally an Interrupt pin + +#define REC_BUTTON 7 + +Adafruit_VS1053_FilePlayer musicPlayer = Adafruit_VS1053_FilePlayer(RESET, CS, DCS, DREQ, CARDCS); + +File recording; // the file we will save our recording to +#define RECBUFFSIZE 128 // 64 or 128 bytes. +uint8_t recording_buffer[RECBUFFSIZE]; + +void setup() { + Serial.begin(9600); + Serial.println("Adafruit VS1053 Wav Recording Test"); + + // initialise the music player + if (!musicPlayer.begin()) { + Serial.println("VS1053 not found"); + while (1); // don't do anything more + } + + musicPlayer.sineTest(0x44, 500); // Make a tone to indicate VS1053 is working + + + if (!SD.begin(CARDCS)) { + Serial.println("SD failed, or not present"); + while (1); // don't do anything more + } + Serial.println("SD OK!"); + + // Set volume for left, right channels. lower numbers == louder volume! + musicPlayer.setVolume(10,10); + + // when the button is pressed, record! + pinMode(REC_BUTTON, INPUT); + digitalWrite(REC_BUTTON, HIGH); + + musicPlayer.softReset(); +} + +uint8_t isRecording = false; +uint16_t bytesWritten = 0; +uint16_t sampleRate = 8000; + +void loop() { + char filename[15]; + + if (!isRecording && !digitalRead(REC_BUTTON)) { + Serial.println("Begin recording"); + isRecording = true; + + // Check if the file exists already + strcpy(filename, "RECORD00.WAV"); + for (uint8_t i = 0; i < 100; i++) { + filename[6] = '0' + i/10; + filename[7] = '0' + i%10; + // create if does not exist, do not open existing, write, sync after write + if (! SD.exists(filename)) { + break; + } + } + Serial.print("Recording to "); Serial.println(filename); + + createWavTemplate(filename, sampleRate); + + recording = SD.open(filename, FILE_WRITE); + if (! recording) { + Serial.println("Couldn't open file to record!"); + while (1); + } + musicPlayer.startRecordWav(true, sampleRate); // use microphone (for linein, pass in 'false') + } + + if (isRecording) + saveRecordedData(isRecording); + + if (isRecording && digitalRead(REC_BUTTON)) { + + Serial.println("End recording"); + + isRecording = false; + // flush all the data! + bytesWritten = saveRecordedData(isRecording); + + // Note: We need to read a full block before telling the VS1053 to stop recording. + musicPlayer.stopRecordWav(); + + // close it up + recording.close(); + + finalizeWavTemplate(filename); + + Serial.print(bytesWritten); Serial.println(" Bytes Written"); + + musicPlayer.softReset(); + + Serial.print("Playing track "); Serial.println(filename); + + musicPlayer.playFullFile(filename); + Serial.println("Finished Playing track"); + } +} + +uint16_t saveRecordedData(boolean isrecord) { + uint16_t written = 0; + + // read how many words are waiting for us + uint16_t wordswaiting = musicPlayer.recordedWordsWaiting(); + + // try to process 128 words (256 bytes) at a time, for best speed + while (wordswaiting > 128) { + //Serial.print("Waiting: "); Serial.println(wordswaiting); + // for example 128 bytes x 4 loops = 512 bytes + for (int x=0; x < 256/RECBUFFSIZE; x++) { + // fill the buffer! + for (uint16_t addr=0; addr < (RECBUFFSIZE); addr+=2) { + uint16_t t = musicPlayer.recordedReadWord(); + //Serial.println(t, HEX); + recording_buffer[addr] = highByte(t); + recording_buffer[addr+1] = lowByte(t); + } + if (! recording.write(recording_buffer, RECBUFFSIZE)) { + Serial.print("Couldn't write "); Serial.println(RECBUFFSIZE); + while (1); + } + } + // flush 256 bytes at a time + recording.flush(); + written += 128; + wordswaiting -= 128; + } + + if (!isrecord) { + + // The recorder prefers us to stop at the block boundary (128 words for mono, 256 words for stereo). + // Hence we have to wait a bit for the recorder to fill up the block, and read that entire block + // before continuing. + // Here we take note of how many words are waiting when the user let go of the button, and we + // pause until there is a full block able to be read. + + wordswaiting = musicPlayer.recordedWordsWaiting(); + uint16_t wordsToSave = wordswaiting; + + // Pause until a full block is waiting to be read. + while(wordswaiting < 128){ + wordswaiting = musicPlayer.recordedWordsWaiting(); + } + + // Read a full block (128 words for mono) + uint16_t addr = 0; + for (int x=0; x < 128; x++) { + // Read a sample from the recorder + uint16_t t = musicPlayer.recordedReadWord(); + + // If we're reading samples from before the button was released, then write them to the buffer + if(x < wordsToSave){ + recording_buffer[addr] = highByte(t); + recording_buffer[addr+1] = lowByte(t); + addr += 2; + + // If the buffer is full, save it + if (addr >= RECBUFFSIZE) { + if (!recording.write(recording_buffer, addr)) { + Serial.println("Couldn't write!"); while (1); + } + written += addr; + addr = 0; + } + } + } + + // Save any remaining samples + if (addr != 0) { + if (!recording.write(recording_buffer, addr)) { + Serial.println("Couldn't write!"); while (1); + } + written += addr; + } + + recording.flush(); + } + + return written; +} + +// Create a WAV file header +void createWavTemplate(const char* filename, unsigned int sampleRate){ + + if(SD.exists(filename)){ + SD.remove(filename); + } + + File sFile = SD.open(filename, FILE_WRITE); + if(!sFile){ + return; + } else { + + sFile.seek(0); + sFile.write((byte*)"RIFF WAVEfmt ", 16); // Note: the data after "RIFF" will be overwritten with the file size + + // NOTE: Little endian! The order of the data bytes is reversed. + + // Chunk size: 0 0 0 20, format code 0x11 for IMA ADPCM, number channels 0 1, sampleRate (4 bytes) + byte data[] = {20,0,0,0, 0x11,0, 1,0, lowByte(sampleRate),highByte(sampleRate), 0, 0}; + sFile.write((byte*)data,12); + + unsigned int byteRate = 0xfd7; // 4 bit ADPCM 8 kHz Mono + data[0] = lowByte(byteRate); data[1] = highByte(byteRate); data[2] = 0; data[3] = 0; // Data rate: Sample rate * bytes per sample + + data[4] = 0; data[5] = 1; // BlockAlign, in this case it is 16 (0x01 0x00) + + data[6] = 0x04; data[7] = 0; // Bits per sample, in this case 4-bit ADPCM + + data[8] = 0x02; data[9] = 0; // Byte Extra Data + + data[10] = 0xf9; data[11] = 0x01; // Samples per block (505) + + sFile.write((byte*)data, 12); + + sFile.write((byte*)"fact", 4); // SubChunk2ID + + data[0] = 0x04; data[1] = 0x00; data[2] = 0x00; data[3] = 0x00; // SubChunk2Size - in this case 4 bytes + + data[4] = 0x00; data[5] = 0x00; data[6] = 0x00; data[7] = 0x00; // NumOfSamples - will be overwritten when finalizing the wav header + sFile.write((byte*)data,8); + + sFile.write((byte*)"data ", 8); // Start of data portion of the file + + sFile.close(); + } +} + +void finalizeWavTemplate(const char* filename){ + unsigned long fSize = 0; + + #ifdef FILE_APPEND + File sFile = SD.open(filename, FILE_WRITE); + #else + File sFile = SD.open(filename, O_READ | O_WRITE); + #endif + + if(!sFile){ + return; + } + fSize = sFile.size() - 8; + + // Set the file size header after the RIFF placeholder + sFile.seek(4); byte data[4] = {lowByte(fSize),highByte(fSize), (byte)(fSize >> 16), (byte)(fSize >> 24)}; + sFile.write(data, 4); + + // Set the data size header (bytes) + sFile.seek(56); + fSize = fSize - 52; + data[0] = lowByte(fSize); data[1]=highByte(fSize); data[2]=(byte)(fSize >> 16); data[3]= (byte)(fSize >> 24); + sFile.write((byte*)data,4); + + // Set the number of samples header (for IMA ADPCM this is: numBlocks * 505) + unsigned long numSamples = fSize / 256 * 505; // For IMA ADPCM there are 256 bytes to a block. + data[0] = lowByte(numSamples); data[1] = highByte(numSamples); data[2] = (byte)(numSamples >> 16); data[3] = (byte)(numSamples >> 24); + sFile.seek(48); + sFile.write((byte*)data,4); + + sFile.close(); +}