Rate Reduction effect for audio.h - code and questions

Status
Not open for further replies.

john-mike

Well-known member
I've been messing with audio.h a bit and decided to make a new effect. I stated with the code for the multiply effect and made this sample and hold / rate reducer / bit crusher.

It works ok but I have some questions about how.

"pa" is a pointer to the audio block but also the counter going through the block and the contents of the block?
What do "*pa++ = audio_in0;" and "mod_in0 = *pb++;" mean exactly? It's incrementing but also... what? They come from the multiply code, just renamed.


effect_sh.h:
Code:
#ifndef effect_sh_h_
#define effect_sh_h_
#include "AudioStream.h"
#include "utility/dspinst.h"

#define SAMPLE_AND_HOLD      0
#define RATE_RECUCTION  1
#define BIT_REDUCTION    2

class AudioEffectSH : public AudioStream
{
public:
  AudioEffectSH() : AudioStream(2, inputQueueArray) { }
  virtual void update(void);


private:
  audio_block_t *inputQueueArray[2];
 // short fx_mode;


};

#endif


effect_sh.cpp:

Code:
#include "effect_sh.h"

void AudioEffectSH::update(void){

  audio_block_t *blocka, *blockb;
  uint32_t *pa, *pb, *end;
  static int32_t audio_in0, paudio_in0; //, a56, a78;
  static short ch1,change0;
  static int32_t mod_in0, pmod_in0,b_wut; //, b56, b78;
  static uint32_t prev_sh, ccnt,hold_cnt,rate_cnt,rate_cnt_max;
  blocka = receiveWritable(0);
  blockb = receiveReadOnly(1);
  if (!blocka) {
    if (blockb) release(blockb);
    return;
  }
  if (!blockb) {
    release(blocka);
    return;
  }
  

  pa = (uint32_t *)(blocka->data);
  pb = (uint32_t *)(blockb->data);
  end = (pa + AUDIO_BLOCK_SAMPLES/2);

  while (pa < end) {

    paudio_in0=audio_in0;
    pmod_in0=mod_in0;
    mod_in0 = *pb++;


    if (pmod_in0 >=0 && mod_in0 <0){
      change0=1;
      rate_cnt_max=rate_cnt;
      rate_cnt=0;
    }

    else{
      change0=0;
      rate_cnt++;
    }


    if (change0==1){
       audio_in0 = *pa;
    }

    if (change0==0){
      audio_in0 = paudio_in0;
    }
  

     *pa++ = audio_in0;
    

    /*
    if (millis() - prev_sh>300){  
      prev_sh=millis();
     // Serial.println(hold_len);
      Serial.println(rate_cnt_max);
      Serial.println();
    }  
    */

  }

  transmit(blocka);
  release(blocka);
  release(blockb);

}

Put these in your audio folder and add the line "#include "effect_sh.h"" to audio.h to try it out


sketch:
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>

long prev;

AudioSynthNoisePink    pink1;        
AudioOutputAnalog      dac1;         
AudioEffectSH          SH1;          
AudioSynthWaveformSine sine1;
AudioSynthWaveformSine modulator_sine2;

AudioConnection          patchCord1(sine1, 0, SH1,0);
AudioConnection          patchCord3(modulator_sine2, 0, SH1,1);
AudioConnection          patchCord2(SH1, dac1);


void setup(void)
{
  
  Serial.begin(9600);
  AudioMemory(20);
  pink1.amplitude(1);
  
  sine1.amplitude(1);
  sine1.frequency(100);
  
  modulator_sine2.amplitude(1);
  modulator_sine2.frequency(100);
  
  analogReadResolution(12);
  analogReadAveraging(21);
  
}


void loop(void)
{
  uint32_t a1 = analogRead(A1)*2;
  uint32_t a2 = analogRead(A2)*3;

  
  sine1.frequency(a1);
  modulator_sine2.frequency(a2);

 
  if (millis() - prev>200){  
   prev=millis();

   Serial.println(a2);

  }

}
 
...

"pa" is a pointer to the audio block but also the counter going through the block and the contents of the block?
What do "*pa++ = audio_in0;" and "mod_in0 = *pb++;" mean exactly? It's incrementing but also... what? They come from the multiply code, just renamed.

...
'*pa++=something;' assigns 'something' to the memory location being pointed to by pa before it is incremented; 'something' will (usually) be the same variable type that pa was cast a pointer to and incrementing pa will not move it just '1' memory location forward but instead the number of memory locations for the variable type involved - if it was a char or uint8_t then it will just move 1 memory location forward after all.

'*pa++;' is not well defined to me - I'd prefer the compiler increments the contents of the variable that pa is pointing at for this one but it may just ignore the 'pointing' reference and move the pointer forward instead - if that is the original Author's intent then I wish they removed the '*' - if I was trying to increment the contents of the variable which is being pointed at I would write '*(pa)++;' to make my intent better defined imho - alternatively I would just write 'pa++;' to just move the pointer forward in the array.


Edit: Am not expert btw, a better defined answer may be on its way :)
 
*pa++ - will return the content of the memory location (dereference) before it increments the pointer to the next memory location by sizeof(pa) same goes for *pb++

its sometimes faster to work with pointers when updating and reading the contents of a buffer.
 
oh, I didn't look at the posted code enough to see context for when OP asked '*pa++;' as if it was on a line by itself - "SOMETHING=*pa++;" is very different from "*pa++;"
 
So then "something = *p++;" sets something to the current value of p then increments?
yes, here is an example to show that it works that way:
Code:
int16_t buf[11] = { -5, -4, -3, -2, -1, 0 , 1, 2, 3, 4, 5 };


void setup() {
  // put your setup code here, to run once:
  while (!Serial);
  delay(100);
}


void loop() {
  // put your main code here, to run repeatedly:
  Serial.print("printing 'buffer'  -> ");
  for (int i = 0; i < 11; i++) {
    Serial.printf("%i, ", buf[i]);
  }
  Serial.println();


  int16_t *p = (int16_t *) buf;
  int16_t something;
  
  Serial.print("printing 'pointer' -> ");
  for (int i = 0; i < 11; i++) {
    something = *p++;
    Serial.printf("%i, ", something);
  }
  Serial.println();
  Serial.println();
  delay(5000);
}
 
This page might help.

https://www.eskimo.com/~scs/cclass/notes/sx10b.html

The autoincrement operator ++ (like its companion, --) makes it easy to do two things at once. We've seen idioms like a[i++] which accesses a and simultaneously increments i, leaving it referencing the next cell of the array a. We can do the same thing with pointers: an expression like *ip++ lets us access what ip points to, while simultaneously incrementing ip so that it points to the next element. The preincrement form works, too: *++ip increments ip, then accesses what it points to. Similarly, we can use notations like *ip-- and *--ip.


The audio library is filled with this sort of fairly advanced C syntax. Normally I try to avoid such things in tutorials and example sketches. Inside the library a lot of the code is highly optimized for performance and uses lots of somewhat advanced C pointer stuff.
 
Well I meant that question.

Next one is about basic arithmetic on the blocks. How do I do it properly? Why do these simplified examples yield noise?

This one pretty much just makes white noise. Lots of clipping and folding.
Code:
void AudioEffectSH::update(void){

  audio_block_t *blocka;
  uint32_t *pa,*end;
  blocka = receiveWritable(0);

  if (!blocka) {
    return;
  }


  pa = (uint32_t *)(blocka->data);


  end = (pa + AUDIO_BLOCK_SAMPLES/2);

  while (pa < end) {


    int32_t audio_in0 = *pa;
 
    int32_t t1 =  (audio_in0*mix)>>16;
    
    *pa++ = t1;


  }

  transmit(blocka);
  release(blocka);


}

This seems to be reducing the sampling rate also, but not on purpose this time!
Code:
void AudioEffectSH::update(void){

  audio_block_t *blocka;
  uint32_t *pa,*end;
  blocka = receiveWritable(0);

  if (!blocka) {
    return;
  }


  pa = (uint32_t *)(blocka->data);


  end = (pa + AUDIO_BLOCK_SAMPLES/2);

  while (pa < end) {


    int32_t audio_in0 = *pa;
 
    int32_t t1 =  signed_multiply_32x16b(audio_in0,mix);
    int32_t t2 =  signed_saturate_rshift(t1,32,16);

    *pa++ = t2;


  }

  transmit(blocka);
  release(blocka);


}




I see that the mixer and envelope are modifying multiple samples at once? I think?

But like you said:
The audio library is filled with this sort of fairly advanced C syntax.
 
Why do these simplified examples yield noise?
....
This one pretty much just makes white noise.

Probably because you're treating an array of 16 bit integers as if they are 32 bit numbers, causing every other 16 bit number to become something pretty much random. The audio samples are always 16 bits.

Maybe you're trying to follow some of the existing code which uses very advanced optimization techniques to pull pairs of samples from the buffer as 32 bit numbers? That's a very advanced and difficult technique. For starting, you should only access the buffer as an array of int16_t.
 
Ah ok.
I switched them all but now it seems it's only processing every other block?
All the rest of the code above is the same.

EDIT:
Ooops it was the AUDIO_BLOCK_SAMPLES/2 needed by the 2 sampel at a time multiply code.

The following works:

Code:
#include "effect_sh.h"


void AudioEffectSH::update(void){

  audio_block_t *blocka;
  int16_t *pa,*end;
  blocka = receiveWritable(0);

  if (!blocka) {
    return;
  }


  pa = (int16_t *)(blocka->data);


  end = (pa + AUDIO_BLOCK_SAMPLES);

  while (pa < end) {

    int16_t audio_in0 = *pa;
    int16_t t1 =  (audio_in0*mix)>>16;
    
    *pa++ = t1;

  }

  transmit(blocka);
  release(blocka);


}
 

Attachments

  • ok-waht.gif
    ok-waht.gif
    33.5 KB · Views: 97
Last edited:
Status
Not open for further replies.
Back
Top