Understanding the Teensy Example Codes

Status
Not open for further replies.

MarkV

Member
hey guys,

i was analysing the teensy 4.0 arduino audio examples yesterday. As you can see below, there are some waveforms implemented in this example and i would like to understand what's happening here:

Code:
case WAVEFORM_SQUARE:
  magnitude15 = signed_saturate_rshift(magnitude, 16, 1);
  for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
   if (ph & 0x80000000) {
    *bp++ = -magnitude15;
   } else {
    *bp++ = magnitude15;
   }
   ph += inc;
  }
  break;


 case WAVEFORM_SAWTOOTH:
  for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
   *bp++ = signed_multiply_32x16t(magnitude, ph);
   ph += inc;
  }
  break;

I really don't get how those signed_multiply_ / signed_shift_ functions work... Which value has *bp after every process?

Is there a possibility to avoid those functions? I want to write my own sawtooth synth code with my own increment function and adding those values to the *bp series, but so far i failed terribly...
 
... have never looked into this before ... but it looks like ::

Audio system works on a block of data fed through the system - the block is prepared as needed in 'advance' and then the system clocks through it, when the block is exhausted it has to 'create' the next block of data to 'continuously' present the desired WAVE output.

This is the data block creation phase for the continuation of output of the desired waveform where bp is the data in the next block:
Code:
	[B]block = allocate();[/B]
...
	[B]bp = block->data;[/B]

The multiply appears to be one of the magical low level DSP instructions that does a number of things at once:
Code:
[B]// computes ((a[31:0] * b[31:16]) >> 16)[/B]
static inline int32_t signed_multiply_32x16t(int32_t a, uint32_t b)
{
	int32_t out;
	[B]asm volatile("smulwt %0, %1, %2" : "=r" (out) : "r" (a), "r" (b));[/B]
	return out;
}

In a SINGLE instruction it does:
SMULWT R4, R5, R3 ; Multiplies R5 with the top halfword of R3, extracts top 32 bits and writes to R4

The multiply is needed to step the given 'magnitude' value through the 'ph==phase' as the block of data it stepped through by the audio system.

That 'magical' instruction does not exist on T_LC so the same code to do that multiple in removed "#ifdef"'s above would be:
Code:
static inline int32_t signed_multiply_32x16t(int32_t a, uint32_t b)
{
	return ((int64_t)a * (int16_t)(b >> 16)) >> 16;
}
Where the T_LC has to jump to 64 bit integer to preserve the high order bits in the multiply, and do two other shifts in the process to get the same result.

Those 'magical' DSP instructions are what Paul uses throughout the Audio system to make it work so efficiently as it does :: ...\hardware\teensy\avr\libraries\Audio\utility\dspinst.h
 
Those 'magical' DSP instructions are what Paul uses throughout the Audio system to make it work so efficiently as it does :: ...\hardware\teensy\avr\libraries\Audio\utility\dspinst.h

I would like to add, these dsp instructions are most efficient for 16 bit audio, that is why the audio-library expects 16 bit data.
32-bit, or float audio is possible with other libraries.
 
Dear defragster, dear WMXZ,

thanks for Your answers so far! I've checked out the Assembler Code, You were posting, aswell.

Code:
// computes ((a[31:0] * b[31:16]) >> 16)
static inline int32_t signed_multiply_32x16t(int32_t a, uint32_t b)
{
	int32_t out;
	asm volatile("smulwt %0, %1, %2" : "=r" (out) : "r" (a), "r" (b));
	return out;
}

as pointed out in the top comment, it does: compute ((a[31:0] * b[31:16]) >> 16). But I was wondering if i could write my own oscillator code, where I would generate those "compute ((a[31:0] * b[31:16]) >> 16) values" by myself?



If there would be something like:

Code:
 for (i=0;i<AUDIO_SAMPLE_RATE;i++) {...}

Which values for every i++ should my code write into the final audio-block pointer? Or maybe i could ask a better question:
If there would be a 16 bit sawtooth audio-block signal, which values are transmitted for every i++?
 
AFAIK - Others have done custom waveforms and other things during the code like:
Code:
 case WAVEFORM_SAWTOOTH:
  for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
   *bp++ = signed_multiply_32x16t(magnitude, ph);
   ph += inc;
  }
  break;

Indeed - there is a way to write compliant alternate Audio library code- though again beyond what I've ever looked into.

Whatever ends up in that bp = block->data; memory is what will be heard.
 
If we look at this segment of code they used for the WAVEFORM_SAWTOOTH:

Code:
case WAVEFORM_SAWTOOTH:
  for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
   *bp++ = signed_multiply_32x16t(magnitude, ph);
   ph += inc;
  }
  break;



magnitude is a constant value which is coming from the 16 bit audio block and it is 65536, because a 16-bit integer can store 65536 distinct values.

So I was wondering, could you, instead of using this signed_mupltiply function, write something like:

Code:
case WAVEFORM_SAWTOOTH:
  for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
   *bp++ = ph;
   ph += inc;
  }
  break;


I understand when you guys are telling me that the assembler code is working faster for audio, but i really don't get how I would check those *bp values if they are correct...
 
Status
Not open for further replies.
Back
Top