Realtime Audio Reversing - is it possible using Teensy?

Status
Not open for further replies.

aquaFina

Member
I'm not as smart as most in this forum so I apologize for asking if this is a simple project. I plan on going through the audio tutorial (again) tomorrow...in hopes for things to 'sink in'.


My husband has challenged me to do a Halloween project. I have every Teensy board available, along with audio adapter, psram (4.1), 23LC1024(4.1), and W25Q128FV but after going through many forum posts & github code...I've come to a standstill on how to proceed. I have learned how to solder (even the SMD chips & they passed the tests) but now need to figure out how to do the actual programming!


What I want to do is create a scary Exorcist effect where the Line-in/Mic audio is processed (this is where I am drawing a blank) and the output is REVERSED. I want it to be actual reverse and not simulated with the audio source being live (not pre-recorded). Is this possible on Teensy? I've only seen examples of reversed audio using pre-recorded wave or raw files with Teensy.


I've looked at the Queue & Delay portions of the audio library but have no clue how to modify the contents appropriately. I do have a Nootropic AudioHacker shield (see: https://github.com/nootropicdesign/.../examples/RealtimeReverse/RealtimeReverse.ino) but unsure how to port over to Teensy.


So...is my idea possible? I am sure I have bitten off more than I can chew...but if it can be done using UNO & the 23LC1024 chip...why not Teensy 4.1 + audio adapter? I'm just so darn clueless and need some pointers.


I know realtime reversing isn't technically possible...but 'near' realtime will be fine. :)


Thank you!
 
If you want a "backward speech effect" I'm wagering all you really neeed to do is play with the envelope of the sound. Alter the attack from an envelope follower by delaying it through an envelope generator. Harder syllables would then come out with a slowly rising attack, I'm betting it would sound something like you want.

EDIT: I looked at the video, it's an interesting effect but not "demonic." I'd look into pitch shifting and envelope manipulation. I'd suggest trying the 19 band vocoder example, that might get you started.
 
So I will study effect_envelope.cpp & .h files. I seem to hit a brick wall not knowing how to properly modify within the ino sketch (or anywhere lol). But that is due to my lack of programming skills. Thank you so much for steering me in the right direction, @boxxofrobots! I will try again tomorrow after a good rest. :)
 
Cool, but I don't think there's any need to write your own effect. I suspect you can build the effect from the library and some glue code. Look at the vocoder - it's hundreds of lines of code written by automation, but the actual loop is really nothing. It's just initialize and it plays.
 
I have SO much to learn. I'm googling 'envelope' audio effect in a moment as I have no musical background. I hope it will be actual reversal of the audio but if not, at least I will be learning more. :) I can't lose this contest against my husband! haha! And thank you again.
 
Simple reversal is by definition not realtime. You could process short samples and reverse each one, but
then you have to have a delay. You also have to have some algorithm for breaking the incoming audio
into sections - spotting syllable or word boundaries for instance is not easy, so this would probably have
to be a fixed period per section.

The most efficient use of buffer memory is boustrophedonic for both input and output, so you'd need to be
able to write and read buffers in either direction.
 
I'm absolutely aware of the falsehood of the term "realtime reverse audio" and I should not have put that in the subject line (oopsie!).

So, essentially a delay...queue...reversal of the queue...then output...with some sort effect (envelope/fade?) of joining between packets (sorry I'm probably not using correct terminology).

But where I get tripped up is...let's say I have queue1 (a packet of audio...pls correct if I'm wrong) -- how do I move the position/index/pointer to the END of the packet and increment from that vantage point? I'm assuming I need to modify a .cpp & .h files for that. Or not? I forgot to mention I also have dyslexia but doesn't matter lol. I'm used to challenges. I really want to surprise my husband but he will definitely critique it. I want to get this one on the "MarkT"! ;)

BTW...@MarkT: I had to google "boustrophedonic" as reading that word caused an ice pick sensation in my head lol. But you are correct! Did you see the link to the AudioHacker script? How difficult would it be to port that to Teensy 4.0 or 4.1? I'm sure I'm over complicating things. :(

But let's say I grab some audio into a buffer. My biggest question is: How do I access the buffer in the .ino sketch? How do I modify the readAddress so it starts from the end and increments towards the beginning?

I'm annoying myself so will leave it at that. I need to sit down, get dirty (with code), and try harder. Then I will post my failed code. :)
 
fyi: My background is in computer graphics and I thought I could make the leap into Teensy but my health issues...(previous brain infection + surgery requiring daily iv abx for 3+ months) make connections more difficult than the average bear/joe. But once things "click"...I'm like Rain Man! I just have no clickage yet with finding access to modify audio buffers. Apologies for the tmi too.
 
Last edited:
Hi, I had a hack at a set of files for you to work on. They will do a basic reverser like your Nootropic link in post #1.

The example is set up to take audio through line in but you should be able to change to a mic.

There is no crossfading so you'll get some pops and clicks.

Cheers, Paul


example.ino
Code:
#include <Audio.h>
#include <SD.h>
#include <SPI.h>
#include <SerialFlash.h>
#include <Wire.h>

#include "effect_reverser.h"

// GUItool: begin automatically generated code
AudioInputI2S i2s1;            // xy=188,102
AudioEffectReverser reverser;  // xy=408,104
AudioOutputI2S i2s2;           // xy=619,107
AudioConnection patchCord1(i2s1, 0, reverser, 0);
AudioConnection patchCord2(reverser, 0, i2s2, 0);
AudioConnection patchCord3(reverser, 0, i2s2, 1);
AudioControlSGTL5000 sgtl5000_1;  // xy=211,214
// GUItool: end automatically generated code

#define DELAYLINE_MAX_LEN 80000  // number of samples at 44100 samples a second
int16_t delay_line[DELAYLINE_MAX_LEN] = {};

void setup() {
  // Enable the audio shield and set the output volume.
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN);
  sgtl5000_1.volume(0.5);

  // alocate some audio memory, don't need much as passing an array to the effect
  AudioMemory(15);
  
  // start up the effect and pass it an array to store the samples
  reverser.begin(delay_line, DELAYLINE_MAX_LEN);
}

void loop() {
  // do stuff
}


header file for custom effect

effect_reverser.h
Code:
#ifndef effect_reverser_h_
#define effect_reverser_h_
#include "Arduino.h"
#include "AudioStream.h"

class AudioEffectReverser : public AudioStream {
 public:
  AudioEffectReverser(void) : AudioStream(1, inputQueueArray) {}
  
  // initialise the delay line
  void begin(int16_t *delay_line, uint32_t max_delay_length);

  // main update routine
  virtual void update(void);

  void inspect(void) { dump_samples = true; };

 private:
  audio_block_t *inputQueueArray[1];
  uint32_t max_delay_length_samples, half_delay_length_samples;  // lenght of the delay line in samples
  uint32_t write_index;               // write head position
  uint32_t read_index;      // read_index
  int16_t *sample_delay_line;  // pointer to delay line

  boolean buffer_filled = false;

  boolean dump_samples = false;
};
#endif

effect_reverser.cpp
Code:
#include <Arduino.h>

#include "effect_reverser.h"

void AudioEffectReverser::begin(int16_t *delay_line, uint32_t max_delay_length) {
  sample_delay_line = delay_line;
  max_delay_length_samples = max_delay_length - 1;
  read_index = half_delay_length_samples = max_delay_length_samples / 2;
  write_index = 0;
}

void AudioEffectReverser::update(void) {
  audio_block_t *block;
  int16_t *block_pointer;

// check if have a delay line
  if (sample_delay_line == NULL) return;

  // grab the block of samples as writeable
  block = receiveWritable();
  if (block) {
    block_pointer = block->data;

    // process each smaple in the block
    for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
      // write the sample to a buffer
      sample_delay_line[write_index++] = *block_pointer;

      // if enought in the buffer start reversing
      if (write_index >= half_delay_length_samples) buffer_filled = true;
      if (buffer_filled) *block_pointer = sample_delay_line[read_index--];

      // wrap round the write index
      if (write_index >= max_delay_length_samples) write_index = 0;

      // wrap round the read index
      if (read_index == 0) read_index = max_delay_length_samples;

      // move the block_pointer on
      block_pointer++;
    }
    // transmit the block out on channel 0
    transmit(block, 0);
    release(block);
  }
}
 
Thanks for the code

Hi, I had a hack at a set of files for you to work on. They will do a basic reverser like your Nootropic link in post #1.

The example is set up to take audio through line in but you should be able to change to a mic.

There is no crossfading so you'll get some pops and clicks.

Cheers, Paul


example.ino
Code:
#include <Audio.h>
#include <SD.h>
#include <SPI.h>
#include <SerialFlash.h>
#include <Wire.h>

#include "effect_reverser.h"

// GUItool: begin automatically generated code
AudioInputI2S i2s1;            // xy=188,102
AudioEffectReverser reverser;  // xy=408,104
AudioOutputI2S i2s2;           // xy=619,107
AudioConnection patchCord1(i2s1, 0, reverser, 0);
AudioConnection patchCord2(reverser, 0, i2s2, 0);
AudioConnection patchCord3(reverser, 0, i2s2, 1);
AudioControlSGTL5000 sgtl5000_1;  // xy=211,214
// GUItool: end automatically generated code

#define DELAYLINE_MAX_LEN 80000  // number of samples at 44100 samples a second
int16_t delay_line[DELAYLINE_MAX_LEN] = {};

void setup() {
  // Enable the audio shield and set the output volume.
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN);
  sgtl5000_1.volume(0.5);

  // alocate some audio memory, don't need much as passing an array to the effect
  AudioMemory(15);
  
  // start up the effect and pass it an array to store the samples
  reverser.begin(delay_line, DELAYLINE_MAX_LEN);
}

void loop() {
  // do stuff
}


header file for custom effect

effect_reverser.h
Code:
#ifndef effect_reverser_h_
#define effect_reverser_h_
#include "Arduino.h"
#include "AudioStream.h"

class AudioEffectReverser : public AudioStream {
 public:
  AudioEffectReverser(void) : AudioStream(1, inputQueueArray) {}
  
  // initialise the delay line
  void begin(int16_t *delay_line, uint32_t max_delay_length);

  // main update routine
  virtual void update(void);

  void inspect(void) { dump_samples = true; };

 private:
  audio_block_t *inputQueueArray[1];
  uint32_t max_delay_length_samples, half_delay_length_samples;  // lenght of the delay line in samples
  uint32_t write_index;               // write head position
  uint32_t read_index;      // read_index
  int16_t *sample_delay_line;  // pointer to delay line

  boolean buffer_filled = false;

  boolean dump_samples = false;
};
#endif

effect_reverser.cpp
Code:
#include <Arduino.h>

#include "effect_reverser.h"

void AudioEffectReverser::begin(int16_t *delay_line, uint32_t max_delay_length) {
  sample_delay_line = delay_line;
  max_delay_length_samples = max_delay_length - 1;
  read_index = half_delay_length_samples = max_delay_length_samples / 2;
  write_index = 0;
}

void AudioEffectReverser::update(void) {
  audio_block_t *block;
  int16_t *block_pointer;

// check if have a delay line
  if (sample_delay_line == NULL) return;

  // grab the block of samples as writeable
  block = receiveWritable();
  if (block) {
    block_pointer = block->data;

    // process each smaple in the block
    for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
      // write the sample to a buffer
      sample_delay_line[write_index++] = *block_pointer;

      // if enought in the buffer start reversing
      if (write_index >= half_delay_length_samples) buffer_filled = true;
      if (buffer_filled) *block_pointer = sample_delay_line[read_index--];

      // wrap round the write index
      if (write_index >= max_delay_length_samples) write_index = 0;

      // wrap round the read index
      if (read_index == 0) read_index = max_delay_length_samples;

      // move the block_pointer on
      block_pointer++;
    }
    // transmit the block out on channel 0
    transmit(block, 0);
    release(block);
  }
}

Just got a teensy 4.0 and sound board, built it in a small cabinet with inputs,outputs,6 pots and 9 buttons and an oled i2c display.
Your program is one of the first i tried and it's exactly what i imagined it could be. Cant wait to do some mods.
 
hi unawoo, 6 pots and 9 buttons sounds good - you can do a lot with that, have fun :) cheers, Paul
 
Quick note to say an extreme thanks to @houston == Paul (and MarkT & boxofrobots). I was able to get it to work and now integrating it into the final Halloween prop (a creepy doll). If I have time before the reveal to my husband...I may try to get the crossfading in. I have done some forum searches and wondering which ones of these yields success for that: "AudioEffectEnvelope" or "AudioEffectFade"?

Once completed, I will post a short clip on this thread with a clever *thank you* message in reverse. :)
 
Status
Not open for further replies.
Back
Top