Welcome to the engine room.
Feel free to leave when you must. Say goodbye to Cerberus on the way out.
He likes biscuits.
Here is the code for the tone generation.
// a useful structure
typedef struct {
int iT, iM, iD;
} triad_state_t;
// helper function to contain any results within min/max
static inline int16_t clamp16from32(int32_t x) {
if (x > 32767)
return 32767;
if (x < -32768)
return -32768;
return (int16_t) x;
}
// These numbers are the conversions of the frequency (e.g. 400) to the number of
// cycles in 480 samples
#define N_T 120 // 48k/400
#define N_M 96 // 48k/500
#define N_D 80 // 48k/600
// minimal arrays to hold the actual full sine wave for each frequency
static int16_t tabT[N_T];
static int16_t tabM[N_M];
static int16_t tabD[N_D];
// initialise the sine wave arrays
void triad_init(int amp_each)// try 3000..5000
{
// for each frequency component, calculate the "y" value by the formula
// gain (loudness) * sine of 2 * π (radians) * "x" divided by (total number of samples)
for (int i = 0; i < N_T; i++)
tabT[i] = (int16_t) (amp_each * sinf(2.0f * 3.14159265f * i / N_T));
for (int i = 0; i < N_M; i++)
tabM[i] = (int16_t) (amp_each * sinf(2.0f * 3.14159265f * i / N_M));
for (int i = 0; i < N_D; i++)
tabD[i] = (int16_t) (amp_each * sinf(2.0f * 3.14159265f * i / N_D));
}
void fill_triad(int16_t *buf, int frames, triad_state_t *st, int left_enable,
int right_enable) {
int iT = st->iT, iM = st->iM, iD = st->iD;
// for each component, generate the "buf" array
// in this example, Tonic goes into the left speaker
for (int n = 0; n < frames; n++) {
int32_t lmix = (int32_t) tabT[iT];
int16_t ls = clamp16from32(lmix);
// and mediant and dominant go into the right speaker
int32_t rmix = (int32_t) tabM[iM] + (int32_t) tabD[iD];
int16_t rs = clamp16from32(rmix);
// fill the actual buffer entry
buf[2 * n + 0] = left_enable ? ls : 0;
buf[2 * n + 1] = right_enable ? rs : 0;
// and start again when at the end of the single cycle
if (++iT >= N_T)
iT = 0;
if (++iM >= N_M)
iM = 0;
if (++iD >= N_D)
iD = 0;
}
st->iT = iT;
st->iM = iM;
st->iD = iD;
}
triad_state_t ts = { 0, 0, 0 };
#define FRAMES 960 // stereo frames in the buffer
static int16_t tx[FRAMES * 2]; //[FRAMES * 2];// L,R interleaved => 2 halfwords per frame
int main(void) {
...
...
...
// initialise triad, volume peak 4000
triad_init(4000);
// fill the triad buffer
fill_triad(tx, FRAMES, &ts, 1, 1);
// start the SAI unit using circular DMA. It will restart from the beginning
// once it has completed the 480 stereo samples
//
// The buffer contains 480 stereo samples.
// That's 960 individual sample values, half of which are zero
// because we're sending different notes to left and right.
// The DMA sees 1920 bytes because DMA,
// like Cerberus, enjoys causing unnecessary suffering.
HAL_SAI_Transmit_DMA(&hsai_BlockA1, (uint8_t*) tx, FRAMES * 2);
...
...
...
}