Build a Teensy based CDJ - Can it be done?

Rezo

Well-known member
So I have an ambitious project I want to dive into - Build a Teensy based CDJ - Like the Pioneer CDJ1000.
This project is purely for fun right now.

I have a CDJ1000mk3 jog wheel with the pressure sensor and encoder on the way, and I would like to start to put something together, going from basic to a more advanced implementation.

Basic:
Using a T4.1, load an MP3 or WAV file from the SD card, transform it into a RAW file on the fly (if possible), then using a custom built audio object be able to play it, pitch bend the track and scrub the track - all using just the jog wheel and the pressure sensor.

Advance:
Using a T4.1 or a Devboard v5 (SDRAM, SDCARD, LCD etc)
Would like to add temp/pitch shifting, loop support, display support (display waveform, set cue(s), display cursor etc)

I've seen different custom implementations of scrubbing, pitch shifting, BPM detection and so forth, but from what I can gather, the Audio library as it stands won't be enough to accomplish even the basic requirement.

I've found a project that has most of these feature implemented on an STM32F746 - while I can't use the same code, I would like to try and use is as a basis for the structure of the audio playback and manipulation

Here is the IRQ for the SAI that plays back the audio on that project
C:
void SAI2_IRQHandler(void)                                                ////////////////////////////////AUDIO PROCESSING   44K1Hz//////////////////////////////
    {
    //HAL_GPIO_WritePin(GPIOB, LED_TAG_LIST_Pin, GPIO_PIN_SET);
    HAL_SAI_IRQHandler(&hsai_BlockA2);
    HAL_SAI_Transmit_IT(&hsai_BlockA2, SAMPLE, 2);
    
    if(Tbuffer[19]&0x8 && ((slip_play_adr+((slip_position+pitch_for_slip)/10000))<294*all_long))                    //SLIP MODE ENABLE
        {
        slip_position+= pitch_for_slip;
        slip_play_adr+=slip_position/10000;   
        slip_position = slip_position%10000;   
        }
        
    position+= pitch;
    
    if(position>9999)   
        {
        step_position = position/10000;   
        if(reverse==0 && ((play_adr+step_position+3)<=(294*all_long)))                   
            {
            play_adr+= step_position;   
            if(step_position==1)
                {
                LR[0][0] = LR[0][1];
                LR[1][0] = LR[1][1];
                LR[0][1] = LR[0][2];
                LR[1][1] = LR[1][2];
                LR[0][2] = LR[0][3];
                LR[1][2] = LR[1][3];                   
                }
            else
                {
                sdram_adr = play_adr&0xFFFFF;                       
                LR[0][0] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][0];                           
                LR[1][0] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][1];
                sdram_adr = (play_adr+1)&0xFFFFF;
                LR[0][1] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][0];                               
                LR[1][1] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][1];       
                sdram_adr = (play_adr+2)&0xFFFFF;
                LR[0][2] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][0];                                   
                LR[1][2] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][1];
                }
            sdram_adr = (play_adr+3)&0xFFFFF;   
            LR[0][3] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][0];                           
            LR[1][3] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][1];       
            }
        else if(reverse==1 && play_adr>=step_position)
            {
            play_adr-= step_position;
            if(step_position==1)
                {
                LR[0][0] = LR[0][1];
                LR[1][0] = LR[1][1];
                LR[0][1] = LR[0][2];
                LR[1][1] = LR[1][2];
                LR[0][2] = LR[0][3];
                LR[1][2] = LR[1][3];
                sdram_adr = (play_adr)&0xFFFFF;   
                LR[0][3] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][0];                           
                LR[1][3] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][1];       
                }
            else
                {
                sdram_adr = play_adr&0xFFFFF;                       
                LR[0][3] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][0];                           
                LR[1][3] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][1];
                sdram_adr = (play_adr+1)&0xFFFFF;
                LR[0][2] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][0];                               
                LR[1][2] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][1];       
                sdram_adr = (play_adr+2)&0xFFFFF;
                LR[0][1] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][0];                                   
                LR[1][1] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][1];
                sdram_adr = (play_adr+3)&0xFFFFF;   
                LR[0][0] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][0];                           
                LR[1][0] = PCM[(sdram_adr>>13)+offset_adress][sdram_adr&0x1FFF][1];           
                }   
            }
        position = position%10000;   
        }   

    T = position;
    T = T/10000;
    T = T - 1/2.0F;
    
    even1 = LR[0][2];
    even1 = even1 + LR[0][1];
    odd1 = LR[0][2];
    odd1 = odd1 - LR[0][1];
    even2 = LR[0][3];
    even2 = even2 + LR[0][0];
    odd2 = LR[0][3];
    odd2 = odd2 - LR[0][0];
    c0 = (float)even1*COEF[0];
    r0 = (float)even2*COEF[1];
    c0 = c0 + r0;
    c1 = (float)odd1*COEF[2];
    r1 = (float)odd2*COEF[3];
    c1 = c1 + r1;
    c2 = (float)even1*COEF[4];
    r2 = (float)even2*COEF[5];
    c2 = c2 + r2;
    c3 = (float)odd1*COEF[6];
    r3 = (float)odd2*COEF[7];
    c3 = c3 + r3;

    SAMPLE_BUFFER = c0+T*(c1+T*(c2+T*c3));
    SAMPLE_BUFFER = SAMPLE_BUFFER*0.90F;
    PCM_2[0] = (int)SAMPLE_BUFFER;

    even1 = LR[1][2];
    even1 = even1 + LR[1][1];
    odd1 = LR[1][2];
    odd1 = odd1 - LR[1][1];
    even2 = LR[1][3];
    even2 = even2 + LR[1][0];
    odd2 = LR[1][3];
    odd2 = odd2 - LR[1][0];
    c0 = (float)even1*COEF[0];
    r0 = (float)even2*COEF[1];
    c0 = c0 + r0;
    c1 = (float)odd1*COEF[2];
    r1 = (float)odd2*COEF[3];
    c1 = c1 + r1;
    c2 = (float)even1*COEF[4];
    r2 = (float)even2*COEF[5];
    c2 = c2 + r2;
    c3 = (float)odd1*COEF[6];
    r3 = (float)odd2*COEF[7];
    c3 = c3 + r3;

    SAMPLE_BUFFER = c0+T*(c1+T*(c2+T*c3));
    SAMPLE_BUFFER = SAMPLE_BUFFER*0.90F;
    PCM_2[1] = (int)SAMPLE_BUFFER;
    
    SAMPLE[3] = PCM_2[0]/256;
    SAMPLE[2] = PCM_2[0]%256;
    SAMPLE[1] = PCM_2[1]/256;
    SAMPLE[0] = PCM_2[1]%256;
    //HAL_GPIO_WritePin(GPIOB, LED_TAG_LIST_Pin, GPIO_PIN_RESET);
    }

GitHub for reference: https://github.com/djgreeb/CDJ-1000mk3_new_life_project
Source Code (ZIP): https://drive.google.com/file/d/1VFx4JItAnkkie4v-_Njo-SxVj8lTepl5/view?usp=sharing

I'd like to start off with being able to load an audio file from the SD card, copy it into PSRAM/SDRAM, then play it back using the Audio board.
But, I would like to do this raw, not using the available functions of the audio library, as I will need to make modifications anyways.

Would appreciate any help/guidance.
 
@Remi_Music Thank you for your response, but I am not looking into building a Midi controller
I want to play the audio from the Teensy and manipulate it.

I have source code for this on the STM32F746, and I know it has been done on an STM32F103 as well - so the Teensy is more than capable of doing this.

I need non DMA i2s tranfers, and I have found an implementation of that in vga_t4 library, so I am working getting a WAV file copied into a PSRAM buffer and then transferred to i2s1 using IRQs - no luck yet, I have a LOT of learning to do.
 
So im trying to play SDTEST1.wav from the example files using the interrupt method based on @Jean-Marc vga_t4 non DMA audio implementation, but I am not having any luck. can someone have a go at this and point me to what I am doing wrong?
@PaulStoffregen perhaps you have some insight here?

C++:
#include <SdFat.h>
#include "Audio.h"
#include "Wire.h"


AudioControlSGTL5000     sgtl5000_1;
// SD card chip select pin for Teensy 4.1 (SDIO)
SdFs sd;
FsFile file;

const char *filename = "SDTEST1.wav";

// Define the buffer size
const size_t bufferSize = 1024 * 1024; // 1024 KB
EXTMEM int16_t buffer[bufferSize];

void setup() {
  // Initialize serial communication
  Serial.begin(9600);
  while (!Serial) {} // Wait for the serial monitor to open

  // Initialize the SD card
  if (!sd.begin(SdioConfig(FIFO_SDIO))) {
    Serial.println("SD card initialization failed!");
    return;
  }
  memset((int16_t*)buffer, 0x00, sizeof(buffer));
  begin_audio();
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.5);
  // Open the WAV file
  file = sd.open(filename);
  if (!file) {
    Serial.println("Failed to open file!");
    return;
  }

  // Read the header
  uint8_t header[44];
  if (file.read(header, 44) != 44) {
    Serial.println("Failed to read the header!");
    file.close();
    return;
  }

  // Print the header information (as before)
  Serial.println("WAV File Header:");
  Serial.print("ChunkID: "); printChunkID(header, 0); Serial.println();
  Serial.print("ChunkSize: "); Serial.println(read32(header, 4));
  Serial.print("Format: "); printChunkID(header, 8); Serial.println();
  Serial.print("Subchunk1ID: "); printChunkID(header, 12); Serial.println();
  Serial.print("Subchunk1Size: "); Serial.println(read32(header, 16));
  Serial.print("AudioFormat: "); Serial.println(read16(header, 20));
  Serial.print("NumChannels: "); Serial.println(read16(header, 22));
  Serial.print("SampleRate: "); Serial.println(read32(header, 24));
  Serial.print("ByteRate: "); Serial.println(read32(header, 28));
  Serial.print("BlockAlign: "); Serial.println(read16(header, 32));
  Serial.print("BitsPerSample: "); Serial.println(read16(header, 34));
  Serial.print("Subchunk2ID: "); printChunkID(header, 36); Serial.println();
  Serial.print("Subchunk2Size: "); Serial.println(read32(header, 40));

    // Verify it's a stereo, 16-bit PCM file
  if (read16(header, 22) != 2 || read16(header, 34) != 16) {
    Serial.println("Unsupported WAV format. Only 16-bit stereo PCM is supported.");
    file.close();
    return;
  }


  // Now read PCM data into the buffer
  size_t bytesRead = 0;
  size_t totalBytesRead = 0;
  Serial.println("Reading PCM data...");
  /*
  while ((bytesRead = file.read(buffer, bufferSize)) > 0) {
    totalBytesRead += bytesRead;
    // Process or use the data in buffer here
    Serial.print("Read "); Serial.print(bytesRead); Serial.println(" bytes");
  }
*/

  bytesRead = file.read(buffer, bufferSize);
  Serial.print("Total bytes read: "); Serial.println(totalBytesRead);

  file.close();
}

void loop() {
  // Empty loop
}

void printChunkID(uint8_t* header, int start) {
  for (int i = 0; i < 4; i++) {
    Serial.print((char)header[start + i]);
  }
}

uint32_t read32(uint8_t* buffer, int start) {
  return buffer[start] | (buffer[start + 1] << 8) | (buffer[start + 2] << 16) | (buffer[start + 3] << 24);
}

uint16_t read16(uint8_t* buffer, int start) {
  return buffer[start] | (buffer[start + 1] << 8);
}


FLASHMEM static void set_audioClock(int nfact, int32_t nmult, uint32_t ndiv, bool force) // sets PLL4
{
  if (!force && (CCM_ANALOG_PLL_AUDIO & CCM_ANALOG_PLL_AUDIO_ENABLE)) return;

  CCM_ANALOG_PLL_AUDIO = CCM_ANALOG_PLL_AUDIO_BYPASS | CCM_ANALOG_PLL_AUDIO_ENABLE
           | CCM_ANALOG_PLL_AUDIO_POST_DIV_SELECT(2) // 2: 1/4; 1: 1/2; 0: 1/1
           | CCM_ANALOG_PLL_AUDIO_DIV_SELECT(nfact);

  CCM_ANALOG_PLL_AUDIO_NUM   = nmult & CCM_ANALOG_PLL_AUDIO_NUM_MASK;
  CCM_ANALOG_PLL_AUDIO_DENOM = ndiv & CCM_ANALOG_PLL_AUDIO_DENOM_MASK;
 
  CCM_ANALOG_PLL_AUDIO &= ~CCM_ANALOG_PLL_AUDIO_POWERDOWN;//Switch on PLL
  while (!(CCM_ANALOG_PLL_AUDIO & CCM_ANALOG_PLL_AUDIO_LOCK)) {}; //Wait for pll-lock
 
  const int div_post_pll = 1; // other values: 2,4
  CCM_ANALOG_MISC2 &= ~(CCM_ANALOG_MISC2_DIV_MSB | CCM_ANALOG_MISC2_DIV_LSB);
  if(div_post_pll>1) CCM_ANALOG_MISC2 |= CCM_ANALOG_MISC2_DIV_LSB;
  if(div_post_pll>3) CCM_ANALOG_MISC2 |= CCM_ANALOG_MISC2_DIV_MSB;
 
  CCM_ANALOG_PLL_AUDIO &= ~CCM_ANALOG_PLL_AUDIO_BYPASS;//Disable Bypass
}

FLASHMEM static void config_sai1()
{
  CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON);
  double fs = AUDIO_SAMPLE_RATE_EXACT;
  // PLL between 27*24 = 648MHz und 54*24=1296MHz
  int n1 = 4; //SAI prescaler 4 => (n1*n2) = multiple of 4
  int n2 = 1 + (24000000 * 27) / (fs * 256 * n1);
  double C = (fs * 256 * n1 * n2) / 24000000;
  int c0 = C;
  int c2 = 10000;
  int c1 = C * c2 - (c0 * c2);

  set_audioClock(c0, c1, c2, true);
  // clear SAI1_CLK register locations
  CCM_CSCMR1 = (CCM_CSCMR1 & ~(CCM_CSCMR1_SAI1_CLK_SEL_MASK))
               | CCM_CSCMR1_SAI1_CLK_SEL(2); // &0x03 // (0,1,2): PLL3PFD0, PLL5, PLL4

  n1 = n1 / 2; //Double Speed for TDM

  CCM_CS1CDR = (CCM_CS1CDR & ~(CCM_CS1CDR_SAI1_CLK_PRED_MASK | CCM_CS1CDR_SAI1_CLK_PODF_MASK))
               | CCM_CS1CDR_SAI1_CLK_PRED(n1 - 1) // &0x07
               | CCM_CS1CDR_SAI1_CLK_PODF(n2 - 1); // &0x3f

  IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1 & ~(IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL_MASK))
                    | (IOMUXC_GPR_GPR1_SAI1_MCLK_DIR | IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL(0));  //Select MCLK


  // configure transmitter
  int rsync = 0;
  int tsync = 1;

  I2S1_TMR = 0;
  I2S1_TCR1 = I2S_TCR1_RFW(1);
  I2S1_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP // sync=0; tx is async;
        | (I2S_TCR2_BCD | I2S_TCR2_DIV((1)) | I2S_TCR2_MSEL(1));
  I2S1_TCR3 = I2S_TCR3_TCE;
  I2S1_TCR4 = I2S_TCR4_FRSZ((2-1)) | I2S_TCR4_SYWD((32-1)) | I2S_TCR4_MF
        | I2S_TCR4_FSD | I2S_TCR4_FSE | I2S_TCR4_FSP;
  I2S1_TCR5 = I2S_TCR5_WNW((32-1)) | I2S_TCR5_W0W((32-1)) | I2S_TCR5_FBT((32-1));


  I2S1_RMR = 0;
  I2S1_RCR1 = I2S_RCR1_RFW(1);
  I2S1_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_RCR2_BCP  // sync=0; rx is async;
        | (I2S_RCR2_BCD | I2S_RCR2_DIV((1)) | I2S_RCR2_MSEL(1));
  I2S1_RCR3 = I2S_RCR3_RCE;
  I2S1_RCR4 = I2S_RCR4_FRSZ((2-1)) | I2S_RCR4_SYWD((32-1)) | I2S_RCR4_MF
        | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD;
  I2S1_RCR5 = I2S_RCR5_WNW((32-1)) | I2S_RCR5_W0W((32-1)) | I2S_RCR5_FBT((32-1));

  //CORE_PIN23_CONFIG = 3;  // MCLK
  CORE_PIN21_CONFIG = 3;  // RX_BCLK
  CORE_PIN20_CONFIG = 3;  // RX_SYNC
  CORE_PIN7_CONFIG  = 3;  // TX_DATA0
  I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE;
  I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE  | I2S_TCSR_FRDE ;//<-- not using DMA */;
}


static uint16_t * txreg = (uint16_t *)((uint32_t)&I2S1_TDR0 + 2);
static uint16_t cnt = 0;
FASTRUN void AUDIO_isr() {
 
  *txreg = (int16_t*)buffer[cnt];
  cnt = cnt + 1;
  cnt = cnt & (bufferSize*2-1);

  if (cnt == 0) {
    //fillfirsthalf = false;
    //NVIC_SET_PENDING(IRQ_SOFTWARE);
  }
  else if (cnt == bufferSize) {
    //fillfirsthalf = true;
    //NVIC_SET_PENDING(IRQ_SOFTWARE);
    I2S1_TCSR |= 0<<8;
  }
/*
  I2S1_TDR0 = i2s_tx_buffer[cnt];
  cnt = cnt + 1;
  cnt = cnt & (sampleBufferSize-1);
  if (cnt == 0) {
    fillfirsthalf = false;
    NVIC_SET_PENDING(IRQ_SOFTWARE);
  }
  else if (cnt == sampleBufferSize/2) {
    fillfirsthalf = true;
    NVIC_SET_PENDING(IRQ_SOFTWARE);
  }
*/
Serial.printf ("AUDIO_isr cnt: %d, txreg: %d \n", cnt, buffer[cnt]);
}

FLASHMEM void begin_audio(){
  config_sai1();
  attachInterruptVector(IRQ_SAI1, AUDIO_isr);
  NVIC_ENABLE_IRQ(IRQ_SAI1);
  NVIC_SET_PRIORITY(IRQ_QTIMER3, 0);  // 0 highest priority, 255 = lowest priority
  NVIC_SET_PRIORITY(IRQ_SAI1, 127);

  I2S1_TCSR |= 1<<8;  // start generating TX FIFO interrupts

  Serial.print("Audio sample buffer = ");
  Serial.println(bufferSize);
  }
 
So I uncommented CORE_PIN23_CONFIG = 3; // MCLK and now I get some sound, sound like the first 1-2 seconds of the test file playing back at a higher pitch. It will play for 8-10 seconds like this then stop
 
Only thing like this with recurring timer interrupts worked with here was the TALKIE code.


It uses a timer interrupt calling: static void sayisr()
 
@defragster thanks for the links. Unfortunately as they don't use the SAI/I2S interface, they are of no help.

I made some changes - now I can get it to play until the buffer is full
C++:
#include "SdFat.h"
#include "Audio.h"
#include "Wire.h"

AudioControlSGTL5000     sgtl5000_1;
// SD card chip select pin for Teensy 4.1 (SDIO)
SdFs sd;
FsFile file;
const char *filename = "SDTEST1.wav";
// Define the buffer size
#define BLOCK_SIZE (1024*1024) // 1024 KB
EXTMEM int16_t i2s_tx_buffer[BLOCK_SIZE];

void begin_audio();
void printChunkID(uint8_t* header, int start);
uint32_t read32(uint8_t* buffer, int start);
uint16_t read16(uint8_t* buffer, int start);
static void set_audioClock(int nfact, int32_t nmult, uint32_t ndiv);
void setup() {
  // Initialize serial communication
  Serial.begin(9600);
  while (!Serial) {} // Wait for the serial monitor to open
  if(CrashReport){
    Serial.print(CrashReport);
  }
  delay (1000);
  // Initialize the SD card
  if (!sd.begin(SdioConfig(FIFO_SDIO))) {
    Serial.println("SD card initialization failed!");
    return;
  }
  memset(i2s_tx_buffer, 0, BLOCK_SIZE * sizeof(int16_t));
  begin_audio();
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.5);
  // Open the WAV file
  file = sd.open(filename);
  if (!file) {
    Serial.println("Failed to open file!");
    return;
  }
  // Read the header
  uint8_t header[44];
  if (file.read(header, 44) != 44) {
    Serial.println("Failed to read the header!");
    file.close();
    return;
  }
  // Print the header information (as before)
  Serial.println("WAV File Header:");
  Serial.print("ChunkID: "); printChunkID(header, 0); Serial.println();
  Serial.print("ChunkSize: "); Serial.println(read32(header, 4));
  Serial.print("Format: "); printChunkID(header, 8); Serial.println();
  Serial.print("Subchunk1ID: "); printChunkID(header, 12); Serial.println();
  Serial.print("Subchunk1Size: "); Serial.println(read32(header, 16));
  Serial.print("AudioFormat: "); Serial.println(read16(header, 20));
  Serial.print("NumChannels: "); Serial.println(read16(header, 22));
  Serial.print("SampleRate: "); Serial.println(read32(header, 24));
  Serial.print("ByteRate: "); Serial.println(read32(header, 28));
  Serial.print("BlockAlign: "); Serial.println(read16(header, 32));
  Serial.print("BitsPerSample: "); Serial.println(read16(header, 34));
  Serial.print("Subchunk2ID: "); printChunkID(header, 36); Serial.println();
  Serial.print("Subchunk2Size: "); Serial.println(read32(header, 40));
    // Verify it's a stereo, 16-bit PCM file
  if (read16(header, 22) != 2 || read16(header, 34) != 16) {
    Serial.println("Unsupported WAV format. Only 16-bit stereo PCM is supported.");
    file.close();
    return;
  }

  // Now read PCM data into the buffer
  size_t bytesRead = 0;
  size_t totalBytesRead = 0;
  Serial.println("Reading PCM data...");
  /*
  while ((bytesRead = file.read(buffer, bufferSize)) > 0) {
    totalBytesRead += bytesRead;
    // Process or use the data in buffer here
    Serial.print("Read "); Serial.print(bytesRead); Serial.println(" bytes");
  }
*/
  delay(100);
  bytesRead = file.read(i2s_tx_buffer, BLOCK_SIZE*sizeof(uint32_t));
  Serial.print("Total bytes read: "); Serial.println(totalBytesRead);
  file.close();
}
void loop() {
  // Empty loop
}
void printChunkID(uint8_t* header, int start) {
  for (int i = 0; i < 4; i++) {
    Serial.print((char)header[start + i]);
  }
}
uint32_t read32(uint8_t* buffer, int start) {
  return buffer[start] | (buffer[start + 1] << 8) | (buffer[start + 2] << 16) | (buffer[start + 3] << 24);
}
uint16_t read16(uint8_t* buffer, int start) {
  return buffer[start] | (buffer[start + 1] << 8);
}

FLASHMEM static void set_audioClock(int nfact, int32_t nmult, uint32_t ndiv) // sets PLL4
{
  CCM_ANALOG_PLL_AUDIO = CCM_ANALOG_PLL_AUDIO_BYPASS | CCM_ANALOG_PLL_AUDIO_ENABLE
           | CCM_ANALOG_PLL_AUDIO_POST_DIV_SELECT(2) // 2: 1/4; 1: 1/2; 0: 1/1
           | CCM_ANALOG_PLL_AUDIO_DIV_SELECT(nfact);
  CCM_ANALOG_PLL_AUDIO_NUM   = nmult & CCM_ANALOG_PLL_AUDIO_NUM_MASK;
  CCM_ANALOG_PLL_AUDIO_DENOM = ndiv & CCM_ANALOG_PLL_AUDIO_DENOM_MASK;
  
  CCM_ANALOG_PLL_AUDIO &= ~CCM_ANALOG_PLL_AUDIO_POWERDOWN;//Switch on PLL
  while (!(CCM_ANALOG_PLL_AUDIO & CCM_ANALOG_PLL_AUDIO_LOCK)) {}; //Wait for pll-lock
  
  const int div_post_pll = 1; // other values: 2,4
  CCM_ANALOG_MISC2 &= ~(CCM_ANALOG_MISC2_DIV_MSB | CCM_ANALOG_MISC2_DIV_LSB);
  if(div_post_pll>1) CCM_ANALOG_MISC2 |= CCM_ANALOG_MISC2_DIV_LSB;
  if(div_post_pll>3) CCM_ANALOG_MISC2 |= CCM_ANALOG_MISC2_DIV_MSB;
  
  CCM_ANALOG_PLL_AUDIO &= ~CCM_ANALOG_PLL_AUDIO_BYPASS;//Disable Bypass
}
FLASHMEM static void config_sai1()
{
  CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON);  
  //PLL:
  int fs = AUDIO_SAMPLE_RATE_EXACT;
  // PLL between 27*24 = 648MHz und 54*24=1296MHz
  int n1 = 4; //SAI prescaler 4 => (n1*n2) = multiple of 4
  int n2 = 1 + (24000000 * 27) / (fs * 256 * n1);
  double C = ((double)fs * 256 * n1 * n2) / 24000000;
  int c0 = C;
  int c2 = 10000;
  int c1 = C * c2 - (c0 * c2);
  set_audioClock(c0, c1, c2);
  // clear SAI1_CLK register locations
  CCM_CSCMR1 = (CCM_CSCMR1 & ~(CCM_CSCMR1_SAI1_CLK_SEL_MASK))
       | CCM_CSCMR1_SAI1_CLK_SEL(2); // &0x03 // (0,1,2): PLL3PFD0, PLL5, PLL4
  CCM_CS1CDR = (CCM_CS1CDR & ~(CCM_CS1CDR_SAI1_CLK_PRED_MASK | CCM_CS1CDR_SAI1_CLK_PODF_MASK))
       | CCM_CS1CDR_SAI1_CLK_PRED(n1-1) // &0x07
       | CCM_CS1CDR_SAI1_CLK_PODF(n2-1); // &0x3f
  // Select MCLK
  IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1
    & ~(IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL_MASK))
    | (IOMUXC_GPR_GPR1_SAI1_MCLK_DIR | IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL(0));

  int rsync = 0;
  int tsync = 1;
  I2S1_TMR = 0;
  //I2S1_TCSR = (1<<25); //Reset
  I2S1_TCR1 = I2S_TCR1_RFW(1);
  I2S1_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP // sync=0; tx is async;
        | (I2S_TCR2_BCD | I2S_TCR2_DIV((1)) | I2S_TCR2_MSEL(1));
  I2S1_TCR3 = I2S_TCR3_TCE;
  I2S1_TCR4 = I2S_TCR4_FRSZ((2-1)) | I2S_TCR4_SYWD((32-1)) | I2S_TCR4_MF
        | I2S_TCR4_FSD | I2S_TCR4_FSE | I2S_TCR4_FSP;
  I2S1_TCR5 = I2S_TCR5_WNW((32-1)) | I2S_TCR5_W0W((32-1)) | I2S_TCR5_FBT((32-1));
  I2S1_RMR = 0;
  //I2S1_RCSR = (1<<25); //Reset
  I2S1_RCR1 = I2S_RCR1_RFW(1);
  I2S1_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_RCR2_BCP  // sync=0; rx is async;
        | (I2S_RCR2_BCD | I2S_RCR2_DIV((1)) | I2S_RCR2_MSEL(1));
  I2S1_RCR3 = I2S_RCR3_RCE;
  I2S1_RCR4 = I2S_RCR4_FRSZ((2-1)) | I2S_RCR4_SYWD((32-1)) | I2S_RCR4_MF
        | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD;
  I2S1_RCR5 = I2S_RCR5_WNW((32-1)) | I2S_RCR5_W0W((32-1)) | I2S_RCR5_FBT((32-1));
  
  CORE_PIN23_CONFIG = 3;  // MCLK
  CORE_PIN21_CONFIG = 3;  // RX_BCLK
  CORE_PIN20_CONFIG = 3;  // RX_SYNC
  CORE_PIN7_CONFIG  = 3;  // TX_DATA0
  I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE;
  I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE  | I2S_TCSR_FRDE ;//<-- not using DMA */;
}

static int16_t * txreg = (int16_t *)((uint32_t)&I2S1_TDR0 + 2);
static uint32_t cnt = 0;
FASTRUN void AUDIO_isr() {
  
  *txreg = (int16_t)i2s_tx_buffer[cnt]; 
  cnt++;
  //cnt = cnt & (BLOCK_SIZE*2-1);
  if (cnt == 0) {
    Serial.println("cnt = 0");
    //fillfirsthalf = false;
    //NVIC_SET_PENDING(IRQ_SOFTWARE);
  } 
  else if (cnt == (BLOCK_SIZE*sizeof(int16_t))){
    Serial.println("IRQ done");
    I2S1_TCSR &= ~(1 << 8);  // stop generating TX FIFO interrupts
    cnt = 0;
    //fillfirsthalf = true;
    //NVIC_SET_PENDING(IRQ_SOFTWARE);
  }
/*
  I2S1_TDR0 = i2s_tx_buffer[cnt]; 
  cnt = cnt + 1;
  cnt = cnt & (sampleBufferSize-1);
  if (cnt == 0) {
    fillfirsthalf = false;
    NVIC_SET_PENDING(IRQ_SOFTWARE);
  } 
  else if (cnt == sampleBufferSize/2) {
    fillfirsthalf = true;
    NVIC_SET_PENDING(IRQ_SOFTWARE);
  }
*/
}
FLASHMEM void begin_audio(){
  config_sai1();
  attachInterruptVector(IRQ_SAI1, AUDIO_isr);
  NVIC_ENABLE_IRQ(IRQ_SAI1); 
  NVIC_SET_PRIORITY(IRQ_SAI1, 127);
  I2S1_TCSR |= 1<<8;  // start generating TX FIFO interrupts
  Serial.println("Audio started");
  }

Now I will work on creating two buffer and swapping between them for a continues audio stream.
 
Great you got playing from a buffer.
thanks for the links.
Indeed, it uses some cool timing to make sound from coded values adjusting DAC type output values. Read that timing was an issue and this uses interrupts to feed it not DMA - other than that not seen anything doing timed sound value updates outside the Audio Lib.
 
Finally had a spare hour to mess around with this, achieved the following
1. Continuously load wav data into a 3 dimension PCM buffer from the SD card - buffer is in PSRAM
2. Interrupt based PCM data transfers to the Audio board - no DMA used
3. Interpolation that allows me to alter pitch and play direction in realtime!

Got loads more to do, but I am REALLY excited in all I have learned in the few hours I have spent playing around with this project.

Will post some code at a later stage
 
Im seeing great progress with my project, and now I am able to pre analyze the PCM data to create a waveform in a buffer, and display that with the playback in a 320px wide by 160px high window (every pixel is 1/150th of a sample, so 320px wide is roughly 2.13 seconds of audio at x1 zoom.
The issue I am facing is that the quiet parts of the audio are not visible at all.
Here is how I create the waveform (latest variant):

Code:
uint8_t WFORMDYNAMIC[145000];
#define DISPLAY_HEIGHT 160
void createWaveform(ExFile file) {
  // Skip WAV header (44 bytes)
file.seekSet(44);

uint32_t numSamplesProcessed = 0;
const uint8_t MIN_VISIBLE_AMPLITUDE = 1; // Minimum non-zero value for visibility
const uint8_t MAX_VISIBLE_AMPLITUDE = DISPLAY_HEIGHT - 1;

// Loop through PCM data
while (file.available()) {
    int16_t maxPositive = INT16_MIN;
    int16_t minNegative = INT16_MAX;
    int32_t sumSquares = 0;

    for (uint16_t i = 0; i < NUM_SAMPLES; i++) {
        int16_t leftChannel, rightChannel;

        if (file.read(&leftChannel, sizeof(int16_t)) != sizeof(int16_t)) break;
        if (file.read(&rightChannel, sizeof(int16_t)) != sizeof(int16_t)) break;

        int16_t avg = (leftChannel + rightChannel) / 2;
        sumSquares += avg * avg;

        if (avg > maxPositive) maxPositive = avg;
        if (avg < minNegative) minNegative = avg;
    }

    // Calculate peak-to-peak amplitude
    int16_t amplitude = maxPositive - minNegative;

    // Apply dynamic range compression
    uint8_t compressedAmplitude = map(constrain(amplitude, 0, INT16_MAX), 0, INT16_MAX, 0, 254);

    // Normalize the amplitude to 0-254 range
    uint8_t normalizedAmplitude = map(constrain(compressedAmplitude, MIN_VISIBLE_AMPLITUDE, 254), 0, 254, 0, MAX_VISIBLE_AMPLITUDE);

    // Ensure quiet areas have at least the minimum visible amplitude
    if (normalizedAmplitude < MIN_VISIBLE_AMPLITUDE) {
        normalizedAmplitude = MIN_VISIBLE_AMPLITUDE;
    }

    // Center the waveform vertically
    uint8_t centeredAmplitude = normalizedAmplitude + (DISPLAY_HEIGHT / 2);

    if (numSamplesProcessed < WFORMDYNAMIC_SIZE) {
        WFORMDYNAMIC[numSamplesProcessed] = centeredAmplitude;
        numSamplesProcessed++;
    }
}
  Serial.printf("Number of samples proccessed: %d \n", numSamplesProcessed);
  //Skip back to end of WAV header/start of PCM data
  file.seekSet(44);
}

And here is how I print it onto the screen:


Code:
#define BLUE_COLOR 0x0000FF // Example color value for blue
#define WAVEFOMR_WIDTH 320
#define WAVEFORM_HIGHT 160

void lv_draw_vline(uint16_t x, uint16_t y, uint8_t length, lv_color_t color){
  for(int i=y; i<length; i++){
    lv_canvas_set_px(waveform_canvas, x, i, color , LV_OPA_100);
  }
}

void DrawDynamicWaveform(uint8_t *WFORMDYNAMIC, uint32_t position, uint8_t DynamicWaveformZOOM) {
    uint32_t adr;
    uint8_t amplitude;
    uint16_t i, j, k;
    lv_layer_t layer;
    position = position/DynamicWaveformZOOM;

    static uint32_t _position = 0xFFFFFFFF;
    if(position == _position) return;
    _position = position;

    memset(waveformbuffer, 0xFFFF, 320*160*2);
    lv_canvas_init_layer(waveform_canvas, &layer);
   
   
    for (i = 0; i < WAVEFOMR_WIDTH; i++) {
        adr = DynamicWaveformZOOM * (i + position - (WAVEFOMR_WIDTH/2));
        //Serial.println(adr);
        if(adr<=all_long){
          amplitude = map(WFORMDYNAMIC[adr],0,159,0, 159);
          if (DynamicWaveformZOOM == 1) {
              lv_draw_vline(i, WAVEFORM_HIGHT - amplitude, amplitude, lv_palette_main(LV_PALETTE_BLUE));
          }
          else {
            uint8_t amplitude_z = WFORMDYNAMIC[adr];
            for (j = 0; j < DynamicWaveformZOOM-1; j++) {
                if (WFORMDYNAMIC[adr+j+1] > amplitude_z) {
                    amplitude_z = WFORMDYNAMIC[adr+j+1];
                }
            }
            amplitude_z = map(amplitude_z,0,159,0, 159);
            lv_draw_vline(i, WAVEFORM_HIGHT - amplitude_z, amplitude_z, lv_palette_main(LV_PALETTE_BLUE));
          }
        }
        else{
          lv_canvas_set_px(waveform_canvas, i, WAVEFORM_HIGHT/2, lv_palette_main(LV_PALETTE_BLUE) , LV_OPA_100);
        }

   
    }
    lv_draw_vline(159, 0, 159, lv_color_hex(0x000000));
    lv_draw_vline(160, 0, 159, lv_color_hex(0x000000));
    lv_canvas_finish_layer(waveform_canvas, &layer);
}

The printing on the screen part is solid, im confient of that,
But, the algorithem used to create the waveform is not cutting it.

Can someone help me perfect it?
Im looking to achieve something like this (center of screen, but also plan for lower static waveform):
01_PLAY_PHASE-METER-TYPE1.jpg
 
A logarithmic view of the amplitude should help to get the quiet parts visible: Use 20*log10 to get a view in decibels.
 
@TomChiron thanks for the suggestion!
Would it be better to convert the amplitude to a float at any point and then run log10?
Or should I stay with the int16_t type?
 
For decibels in the usual way the argument you put into log10 has to be between 0.0 (or smallest float if you want to avoid -inf as result) and 1.0. So it makes sense to normalize / use as reference the maximum integer number (32767) with something like:
Code:
float level_dBFS = 20 * log10 (abs(level_lin_int) / 32767)
Or maybe for you it makes sense to use the maximum of the amplitude.
You can also see if it is nice to use the absolute value of the (peak) level values or if you calculate "back" to negative values in the grafical representation. Or if you simply plot it symmetrically. (I am not sure how this is done in commercial devices. In your screenshot it looks completely symmetrical. As this is more for orientation this is more than sufficient).
 
Thanks for explaining that.
Yes, the image above does show a symmetrical waveform, so I believe they are plotting the max values per group and multiplying the length.
So a bit of background, the image above is from a Pioneer XDJ1000 MK2, and the waveforms are pre calcualted with desktop software called Rekordbox, also owned by Pioneer DJ.

Now, someone a few year ago took an old CD based CDJ1000, an STM32F746 and has been able to reverse engineer the Rekordbox export files and display the waveform with the STM
Here is how he did it:
C:
if(DynamicWaveformZOOM==1)
                    {   
                    ForceDrawVLine(i+40, 124-(WFORMDYNAMIC[adr]&0x1F), 2+2*(WFORMDYNAMIC[adr]&0x1F), COLOR_MAP[UTILITY_SETTINGS[7]][WFORMDYNAMIC[adr]>>5]);        //124-125px center   
                    ForceDrawVLine(i+40, 92, 32-(WFORMDYNAMIC[adr]&0x1F), BG_COLOR);                               
                    ForceDrawVLine(i+40, 126+(WFORMDYNAMIC[adr]&0x1F), 32-(WFORMDYNAMIC[adr]&0x1F), BG_COLOR);           
                    }
                else     
                    {
                    uint8_t amplitude = (WFORMDYNAMIC[adr]&0x1F);
                    uint8_t color = (WFORMDYNAMIC[adr]>>5);
                    for(j=0;j<(DynamicWaveformZOOM-1);j++)
                        {
                        if((WFORMDYNAMIC[adr+j+1]&0x1F)>amplitude)
                            {
                            amplitude    = (WFORMDYNAMIC[adr+j+1]&0x1F);
                            if(amplitude>17)
                                {
                                color = (WFORMDYNAMIC[adr+j+1]>>5);
                                }
                            }
                        }       
                    ForceDrawVLine(i+40, 124-amplitude, 2+2*amplitude, COLOR_MAP[UTILITY_SETTINGS[7]][color]);        //124-125px center
                    ForceDrawVLine(i+40, 92, 32-amplitude, BG_COLOR);   
                    ForceDrawVLine(i+40, 126+amplitude, 32-amplitude, BG_COLOR);       
                    }

Note that for each line, he sets y coordinate to an offset of amplitude by 124 (I believe this is the hight of the waveform, as his display is 272px high) and sets the length of the vertical line to draw to 2+2*amplitude
So this confirms the waveform is symmetrical, while mine is not.

So I guess to make it symmetrical I can just find the max value in each group and scale it down to half the height of the waveform, or, I can do the same but with the amplitude
 
Went through a few variations of code, trying to make it symmetrical with no luck.
Using Logarithmic algorithem makes the waveform look even more empty.

I'm currently suck at a dead end on this. Need to start over from scratch and get it working.
 
Finally got the waveform code working right!
Code:
void createWaveform(ExFile file) {
  // Skip WAV header (44 bytes)
  file.seekSet(44);
  uint32_t numSamplesProcessed = 0;

  // Loop through PCM data
  while (file.available()) {
      
      int32_t maxAmplitude = 0;
      int64_t sumSquares = 0;

      for (uint16_t i = 0; i < NUM_SAMPLES; i++) {
          int16_t leftChannel, rightChannel;

          if (file.read(&leftChannel, sizeof(int16_t)) != sizeof(int16_t)) break;
          if (file.read(&rightChannel, sizeof(int16_t)) != sizeof(int16_t)) break;

          int32_t combinedSample = (leftChannel + rightChannel) / 2;
          int32_t amplitude = abs(combinedSample);  // Ensure amplitude is positive

          if (amplitude > maxAmplitude) {
              maxAmplitude = amplitude;
          }

          sumSquares += (int64_t)amplitude * amplitude;
        }

      // Calculate RMS
      int32_t rms = sqrt(sumSquares / NUM_SAMPLES);

      // Scale amplitude and RMS to 8 bits each (0-WAVEFORM_HIGHT/2-1)
      uint8_t scaledAmplitude = map(maxAmplitude,0, INT16_MAX, 0, WAVEFORM_HIGHT/2-1);
      uint8_t scaledRMS = map(rms,0, INT16_MAX, 0, WAVEFORM_HIGHT/2-1);

      // Combine amplitude and RMS into a uint16_t value
      uint16_t combinedValue = (scaledAmplitude << 8) | scaledRMS;

      if (numSamplesProcessed < WFORMDYNAMIC_SIZE) {
          WFORMDYNAMIC[numSamplesProcessed] = combinedValue;
          numSamplesProcessed++;
      }
  }
  Serial.printf("Number of samples proccessed: %d \n", numSamplesProcessed);
  //Skip back to end of WAV header/start of PCM data
  file.seekSet(44);
}


Now displaying amplitude in Blue and RMS in White
Code:
#define BLUE_COLOR 0x0000FF // Example color value for blue
#define WAVEFOMR_WIDTH 320
#define WAVEFORM_HIGHT 64
static uint8_t waveformbuffer[WAVEFOMR_WIDTH * WAVEFORM_HIGHT * 2];

void lv_draw_vline(uint16_t x, uint16_t y, uint8_t length, lv_color_t color){
    // Loop from y downwards by `length` pixels
    for(int i = y; i < y + length; i++){
        lv_canvas_set_px(waveform_canvas, x, i, color, LV_OPA_100);
    }
}

void DrawDynamicWaveform(uint16_t *WFORMDYNAMIC, uint32_t position, uint8_t DynamicWaveformZOOM) {
    uint32_t adr;
    uint8_t amplitude, rms;
    uint16_t i, j;
    lv_layer_t layer;
    position = position/DynamicWaveformZOOM;

    static uint32_t _position = 0xFFFFFFFF;
    if(position == _position) return;
    _position = position;

    memset(waveformbuffer, 0x0000, WAVEFOMR_WIDTH*WAVEFORM_HIGHT*2);
    lv_canvas_init_layer(waveform_canvas, &layer);
    
    
    for (i = 0; i < WAVEFOMR_WIDTH; i++) {
        adr = DynamicWaveformZOOM * (i + position - (WAVEFOMR_WIDTH/2));
        if(adr<=all_long){
          lv_color_t color = lv_palette_main(LV_PALETTE_BLUE);
          amplitude = WFORMDYNAMIC[adr]>>8;
          rms = WFORMDYNAMIC[adr]&0xFF;
          if (DynamicWaveformZOOM == 1) {
            
              lv_draw_vline(i,(WAVEFORM_HIGHT/2)-amplitude-1, 2+(2*amplitude),color);
              lv_draw_vline(i,(WAVEFORM_HIGHT/2)-rms-1, 2+(2*rms), lv_color_white());
              
          }
          else {
            uint8_t amplitude_z = WFORMDYNAMIC[adr]>>8;
            uint8_t rms_z = WFORMDYNAMIC[adr]&0xFF;
            for (j = 0; j < DynamicWaveformZOOM-1; j++) {
                if (WFORMDYNAMIC[adr+j+1] > amplitude_z) {
                    amplitude_z = WFORMDYNAMIC[adr+j+1] >>8;
                }
                if(WFORMDYNAMIC[adr]&0xFF > rms_z){
                  rms_z = WFORMDYNAMIC[adr+j+1]&0xFF;
                }
            }
            lv_draw_vline(i,(WAVEFORM_HIGHT/2)-amplitude_z-1, 2+(2*amplitude_z), color);
            lv_draw_vline(i,(WAVEFORM_HIGHT/2)-rms_z-1, 2+(2*rms_z), lv_color_white());
          }
        }
        else{
          lv_canvas_set_px(waveform_canvas, i, (WAVEFORM_HIGHT/2) - 1, lv_palette_main(LV_PALETTE_BLUE) , LV_OPA_100);
          lv_canvas_set_px(waveform_canvas, i, WAVEFORM_HIGHT/2, lv_palette_main(LV_PALETTE_BLUE) , LV_OPA_100);
        }

    
    }
    lv_draw_vline(159, 0, 63, lv_color_hex(0xFF0000));
    lv_draw_vline(160, 0, 63, lv_color_hex(0xFF0000));
    lv_canvas_finish_layer(waveform_canvas, &layer);
}
IMG_1532.jpg
 
Continuting work at a very slow pace (in between kids works wife home stuff) and I am now able to also detect BPM as part of my audio track pre analysis

At first, I was going to try port queen-mary dsp tempo detection to the Teensy, but I came across another library, platform independent, that can detect tempo, beats and onset, AND, someone has run it on a Teensy 4.1 before.
A quick 20-30 mins to make a few mods the the library file&folder structure with a quick test sketch and worked on the first flash.



Feeding it with 30-60 seconds worth of WAV data will yield the BPM, and I have confirmed that it's accurate when compared to Mixxx and some online bpm detection sites, and with the config in the sketch below, it will take roughly 12 seconds to process those 60 seconds of audio data


C++:
#include "BTT.h"
#include <SD.h>

//const char *filename = "ATMOSPHERE.wav";
const char *filename = "36M_COLLAR.wav";

const int BUFFER_SIZE = 64;
float audioBuffer[BUFFER_SIZE];
BTT * beatTracker; //our object for everything
uint32_t startTime, endTime;



File audioFile;




void setup() {
  // put your setup code here, to run once:
  // Initialize serial communication for debugging (optional)
  Serial.begin(9600);
  while (!Serial); // Wait for Serial monitor to open

  if (CrashReport) {
    Serial.println(CrashReport);
  }

  /* instantiate a new object */
  //beatTracker = btt_new_default();
  int new_overlap = 2; // 1, 2, 4, or 8 (default)
  float factor = new_overlap/8.0;
  BTT* beatTracker = btt_new(BTT_SUGGESTED_SPECTRAL_FLUX_STFT_LEN * factor,
  BTT_SUGGESTED_SPECTRAL_FLUX_STFT_OVERLAP * factor,
  BTT_SUGGESTED_OSS_FILTER_ORDER,
  BTT_SUGGESTED_OSS_LENGTH * factor,
  BTT_SUGGESTED_ONSET_THRESHOLD_N * factor,
  BTT_SUGGESTED_CBSS_LENGTH * factor,
  BTT_SUGGESTED_SAMPLE_RATE,
  BTT_DEFAULT_ANALYSIS_LATENCY_ONSET_ADJUSTMENT,
  BTT_DEFAULT_ANALYSIS_LATENCY_BEAT_ADJUSTMENT
  );
  float new_coefficient = pow(BTT_DEFAULT_GAUSSIAN_TEMPO_HISTOGRAM_DECAY, 8.0/new_overlap);
  btt_set_gaussian_tempo_histogram_decay (beatTracker, new_coefficient);
  btt_set_num_tempo_candidates(beatTracker, 4);
 
  // Initialize SD card
  if (!SD.begin(BUILTIN_SDCARD)) {
    Serial.println("SD card initialization failed.");
    return;
  }
  Serial.println("SD inited");

  // Open the file
  audioFile = SD.open(filename);
  if (!audioFile) {
    Serial.println("Failed to open file.");
    return;
  }
  Serial.println("File opened");

  // Skip the 44-byte WAV header
  audioFile.seek(44);
 
  // Buffer to hold 64 stereo samples (64 left, 64 right) = 128 samples
  int16_t pcmBuffer[BUFFER_SIZE * 2];
  uint32_t bytesRead = 0;
  startTime = millis();
  while (bytesRead < (44100 * 60 * 2)) {
    // Read 64 stereo samples (128 PCM values)
    bytesRead += audioFile.read(pcmBuffer, sizeof(pcmBuffer));
    //Serial.printf("Bytes Read: %d \n", bytesRead);
    
    // If not enough samples were read, process the last remaining samples
    if (bytesRead < sizeof(pcmBuffer)) {
      //Serial.println("Reached the end of the file.");
      
      // Fill the remaining part of the buffer with zeroes
      for (int i = bytesRead / 2; i < BUFFER_SIZE * 2; i++) {
        pcmBuffer[i] = 0;
      }
    }

    // Convert stereo to mono and normalize to float between -1.0 and 1.0
    for (int i = 0; i < BUFFER_SIZE; i++) {
      int16_t left = pcmBuffer[i * 2];     // Left channel
      int16_t right = pcmBuffer[i * 2 + 1]; // Right channel
      int16_t mono = (left + right) / 2;    // Average to mono

      // Normalize the value (range -32768 to 32767) to float (-1.0 to 1.0)
      audioBuffer[i] = mono / 32768.0;
    }
    btt_process(beatTracker, audioBuffer, BUFFER_SIZE);
    

    // Break loop if this was the last chunk
    if (bytesRead < sizeof(pcmBuffer)) {
      
      break;
    }
  }
  endTime = millis();
  // Convert milliseconds to minutes
  uint32_t elapsedseconds = (endTime-startTime) /1000;
        
  Serial.print("Elapsed time in Seconds: ");
  Serial.println(elapsedseconds);  // Print with 6 decimal places
  Serial.println("Detected tempo: " + String(btt_get_tempo_bpm(beatTracker)));

  // Close the file
  audioFile.close();
 


}

void loop() {
  // put your main code here, to run repeatedly:

}
 
Quick demo of my latest work; got the devboard v5 to run the code, am able to analyze the track and then display a moving waveform
Audio is buffered in SDRAM
Waveform is buffered in SDRAM
Waveform canvas is buffered in DMAMEM
Display buffers are buffered in SDRAM

Display is 800*480px at 16 bit color depth
Waveform is 800*128px
 
Back
Top