Talk - Code

First, an explanation of the output tones. The diagram shows three sine waves. See how they converge at 10mS?
The numbers weren't a guess. They're convenience.

The triad (a major triad, doh, mi, so) sounds "nice". It starts at 400Hz which is the "Doh", or Tonic, indicated by T in the code.
Next is the "Mi", the mediant or third, represented by M, and is 500Hz.
Finally, "Soh", the dominant or fifth, represented by D, at 600Hz.

At 48kHz, which is the sampling rate we've set in the Serial Audio Interface, SAI, these frequencies of 400, 500 and 600Hz are
120, 96 and 80 points per cycle.

So, neatly, the lowest common multiple, the smallest number into which they will divide
without a remainder (remember from school?) is 480.

So why 960, I hear you people paying attention at the front, ask?
Because it's stereo, I reply. Each of the 480 points appears twice: the left channel, then the right, making 960 samples in total.
In this example, if the left channel is set, the right will be zero, and vice-versa.

So the buffer will fill up with alternate left/right samples. Simples!

Thank you, Aleksandr. Now here are the sounds, mixed together, as your ears will hear them.


Wonderful.

Here is the code for the tone generation.


typedef struct {
    int iT, iM, iD;
} triad_state_t;

static inline int16_t clamp16from32(int32_t x) {
    if (x > 32767)
        return 32767;
    if (x < -32768)
        return -32768;
    return (int16_t) x;
}

#define N_T 120   // 48k/400
#define N_M 96   // 48k/500
#define N_D 80   // 48k/600

static int16_t tabT[N_T];
static int16_t tabM[N_M];
static int16_t tabD[N_D];

void triad_init(int amp_each)// try 3000..5000
{
    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));
}

typedef struct {
    int iT, iM, iD;
} triad_state_t;

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 (int n = 0; n < frames; n++) {
        int32_t lmix = (int32_t) tabT[iT];
        int16_t ls = clamp16from32(lmix);

        int32_t rmix = (int32_t) tabM[iM] + (int32_t) tabD[iD];
        int16_t rs = clamp16from32(rmix);

        buf[2 * n + 0] = left_enable ? ls : 0;
        buf[2 * n + 1] = right_enable ? rs : 0;

        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) {
    ...
    ...
    ...

    triad_init(4000);
    fill_triad(tx, FRAMES, &ts, 1, 1);
    HAL_SAI_Transmit_DMA(&hsai_BlockA1, (uint8_t*) tx, FRAMES * 2);

    ...
    ...
    ...
}



Abandon hope, all ye who enter here

Back