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
 
Back
Top