Audiokit: Record to RAM @ 96k sample-rate _while_ playing back at resampled rate? #2087
-
Hi. I've done several projects with arduino-audio-tools and it surprises me all the time. Simply wonderful!
The problem: I get choppy playback during the "full duplex" moment of recording (but it sounds OK during playback). If I reduce the SR to around 88000 then the choppiness is gone, but Ideally I'd like to adapt this code to an I2S ADC with 192k SR for better detail in the ultrasound. Is there something obvious that I'm missing? Many thanks for all you've done already! /**
* ultrasound to audible sound resampling test, based on streams_audiokit_ram_audiokit.ino by Phil Schatzmann
* Hold Key 1 to sample ultrasound and immediately hear it at slower (audible) speed. Playback will loop when button is released.
* Remember to enable PSRAM in Arduino IDE Tools menu
* Requires ESP32 Arduino core 3.x (tested on 3.2.0)
*/
#include "AudioTools.h"
#include "AudioTools/AudioLibs/AudioBoardStream.h"
#include "AudioTools/AudioLibs/MemoryManager.h"
/// USER VARS ///
int recSampleRate = 96000; // input sampling rate
int speedFactors[] = {16, 8, 4, 2, 1, 2, 4, 8}; // available speed factors to use for playback (1/speedFactor)
int sfIndex = 0; // select factor from array to use as startup default
/// INTERNAL VARS ///
bool nowRecording = 0; // are we filling the buffer?
bool nowPlaying = 0; // is there something in the buffer to play?
AudioInfo info(recSampleRate, 2, 16); // for ADC
AudioInfo out_info(recSampleRate / speedFactors[sfIndex],2,16); // for DAC
AudioBoardStream kit(AudioKitEs8388V1);
MemoryManager memory(500); // Activate SPI RAM for objects > 500 bytes
DynamicMemoryStream recording(true); // Audio stored on heap [args: loop (bool), buffer-size (int)]
ResampleStream resample(recording);
StreamCopy copier1(recording, kit); // copies data
StreamCopy copier2(kit, resample); // copies data
auto rcfg = resample.defaultConfig();
void change_sr(bool pinStatus, int pin, void* ref) {
sfIndex++;
if (sfIndex > sizeof(speedFactors) / sizeof(speedFactors[0]) - 1) {
sfIndex = 0;
}
Serial.print("Speed: 1/");
Serial.println(speedFactors[sfIndex]);
out_info.sample_rate = recSampleRate / speedFactors[sfIndex]; // update output sample-rate
rcfg.copyFrom(out_info);
rcfg.step_size = 1.0 / speedFactors[sfIndex]; // update resampler
resample.end(); // restart resampler for changes to take effect
resample.begin(rcfg);
}
void record_start(bool pinStatus, int pin, void* ref){
Serial.print("Recording... (sample-rate: ");
Serial.print(info.sample_rate);
Serial.println(" kHz)");
Serial.print("Playing... (sample-rate: ");
Serial.print(out_info.sample_rate);
Serial.print(" kHz, speed: 1/");
Serial.print(speedFactors[sfIndex]);
Serial.println(")");
recording.end();
recording.begin();
copier1.begin(); // start recording
copier2.begin(); // start playback
nowRecording = 1;
nowPlaying = 1;
}
void record_end(bool pinStatus, int pin, void* ref){
Serial.println("Recording stopped (playback continues) ");
nowRecording = 0;
}
void setup(){
Serial.begin(115200);
while(!Serial); // wait for serial to be ready
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);
// setup input and output
auto cfg = kit.defaultConfig(RXTX_MODE);
cfg.sd_active = false;
cfg.copyFrom(info);
cfg.input_device = ADC_INPUT_LINE2;
kit.begin(cfg);
kit.setInputVolume(1.0); // input
kit.setVolume(0.4); // output
// define resampling info
//auto rcfg = resample.defaultConfig(); // now global
rcfg.copyFrom(out_info);
rcfg.copyFrom(info);
rcfg.step_size = 1.0 / speedFactors[sfIndex];
resample.begin(rcfg);
// record when key 1 is pressed
kit.audioActions().add(kit.getKey(1), record_start, record_end);
// chang playback sample-rate when key 6 is pressed
kit.audioActions().add(kit.getKey(6), change_sr);
Serial.println("Press Key 1 to record.");
Serial.println("Press Key 6 to change playback speed.");
}
void loop(){
// record and play recording (only if a recording has started, otherwise lib throws memory allocation error)
if (nowRecording) {
copier1.copy();
}
if (nowPlaying) {
copier2.copy();
}
//t Process keys
kit.processActions();
} |
Beta Was this translation helpful? Give feedback.
Replies: 6 comments 3 replies
-
I think the following happens: If you play back at a lower speed (while still recording) the copier2.copy(); is taking more time then the copier1.copy() requires data so you run into an underflow. Solution appraches:
|
Beta Was this translation helpful? Give feedback.
-
I decided to use tasks for higher performance, since I hope to increase the sample-rate later. This is my first experience with multi-core processing, so I had trouble with the ESP32 watchdog resetting. I think that streamcopy.copy is safe to put in a task, but since my copiers are conditional, it seemed to lock up the task and trigger the wdt when the condition wasn't met? /**
* ultrasound to audible sound resampling test, based on streams_audiokit_ram_audiokit.ino by Phil Schatzmann
* Hold Key 1 to sample ultrasound and immediately hear it at slower (audible) speed. Playback will loop when button is released.
* Remember to enable PSRAM in Arduino IDE Tools menu
* Requires ESP32 Arduino core 3.x (tested on 3.2.0)
*/
#include "AudioTools.h"
#include "AudioTools/AudioLibs/AudioBoardStream.h"
#include "AudioTools/AudioLibs/MemoryManager.h"
#include "AudioTools/Concurrency/RTOS.h"
/// USER VARS ///
int recSampleRate = 96000; // input sampling rate
int speedFactors[] = {16, 8, 4, 2, 1, 2, 4, 8}; // available speed factors to use for playback (1/speedFactor)
int sfIndex = 0; // select factor from array to use as startup default
/// INTERNAL VARS ///
bool nowRecording = 0; // are we filling the buffer?
bool nowPlaying = 0; // is there something in the buffer to play?
AudioInfo info(recSampleRate, 2, 16); // for ADC
AudioInfo out_info(recSampleRate / speedFactors[sfIndex],2,16); // for DAC
AudioBoardStream kit(AudioKitEs8388V1);
MemoryManager memory(500); // Activate SPI RAM for objects > 500 bytes
DynamicMemoryStream recording(true); // Audio stored on heap [args: loop (bool), buffer-size (int)]
ResampleStream resample(recording);
StreamCopy copier1(recording, kit); // copies data
StreamCopy copier2(kit, resample); // copies data
auto rcfg = resample.defaultConfig(); // set up the resampler config
Task writeTask("rec-copy", 3000, 10, 0);
Task readTask("play-copy", 3000, 10, 1);
void rec_function() {
if (nowRecording) {
copier1.copy();
} else {
delay(1);
}
}
void play_function() {
if (nowPlaying) {
copier2.copy();
} else {
delay(1);
}
}
void change_sr(bool pinStatus, int pin, void* ref) {
sfIndex++;
if (sfIndex > sizeof(speedFactors) / sizeof(speedFactors[0]) - 1) {
sfIndex = 0;
}
Serial.print("Speed: 1/");
Serial.println(speedFactors[sfIndex]);
out_info.sample_rate = recSampleRate / speedFactors[sfIndex]; // update output sample-rate
rcfg.copyFrom(out_info);
rcfg.step_size = 1.0 / speedFactors[sfIndex]; // update resampler
resample.end(); // restart resampler for changes to take effect
resample.begin(rcfg);
}
void record_start(bool pinStatus, int pin, void* ref){
Serial.print("Recording... (sample-rate: ");
Serial.print(info.sample_rate);
Serial.println(" kHz)");
Serial.print("Playing... (sample-rate: ");
Serial.print(out_info.sample_rate);
Serial.print(" kHz, speed: 1/");
Serial.print(speedFactors[sfIndex]);
Serial.println(")");
recording.end();
recording.begin();
copier1.begin(); // start recording
copier2.begin(); // start playback
nowRecording = 1;
nowPlaying = 1;
}
void record_end(bool pinStatus, int pin, void* ref){
Serial.println("Recording stopped (playback continues) ");
nowRecording = 0;
}
void setup(){
Serial.begin(115200);
while(!Serial); // wait for serial to be ready
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);
// setup input and output
auto cfg = kit.defaultConfig(RXTX_MODE);
cfg.sd_active = false;
cfg.copyFrom(info);
cfg.input_device = ADC_INPUT_LINE2;
kit.begin(cfg);
kit.setInputVolume(1.0); // input
kit.setVolume(0.4); // output
// define resampling info
//auto rcfg = resample.defaultConfig(); // now global
rcfg.copyFrom(out_info);
rcfg.copyFrom(info);
rcfg.step_size = 1.0 / speedFactors[sfIndex];
resample.begin(rcfg);
// record when key 1 is pressed
kit.audioActions().add(kit.getKey(1), record_start, record_end);
// chang playback sample-rate when key 6 is pressed
kit.audioActions().add(kit.getKey(6), change_sr);
Serial.println("Press Key 1 to record.");
Serial.println("Press Key 6 to change playback speed.");
// Start audio tasks
writeTask.begin(rec_function);
readTask.begin(play_function);
}
void loop(){
// Process keys
kit.processActions();
}
|
Beta Was this translation helpful? Give feedback.
-
One more thing, the examples on the multicore wiki page https://github.com/pschatzmann/arduino-audio-tools/wiki/Multicore-Processing use this include: |
Beta Was this translation helpful? Give feedback.
-
Yes, if you don't add any delay, the task will never yield and you will run into a watchdog exception. |
Beta Was this translation helpful? Give feedback.
-
You got it right but use a MutexRTOS myMutex; |
Beta Was this translation helpful? Give feedback.
-
Amazing. Works great! I really appreciate that you took the time! |
Beta Was this translation helpful? Give feedback.
I think the following happens: If you play back at a lower speed (while still recording) the copier2.copy(); is taking more time then the copier1.copy() requires data so you run into an underflow.
Solution appraches: