8 years, 1 month ago.

How to implement audio played on set intervals as well as a scan matrix, for a custom drum machine.

Hi,

I've been working on an mbed based drum machine for a bit now. Currently it is functioning but there are issues. I need advice on how to properly implement wav audio playback so that it doesn't interfere with other functions of the code such as scanning a matrix.

A little bit of explanation on the project:

The drum machine is 8 steps, it has a clock that produces intervals at a specific BPM and Musical note which will trigger 8 audio files in a loop. On each of the 8 steps a button and led is assigned which can trigger a 22.1khz wav file to play. The idea is to be able to create a drum loop capable of a range of bpms and musical notes. Which you can interact with in real time to change which of the steps are active to create different rhythms.

The clock of the drum machine is a timer which triggers an interrupt every time it hits the correct time according to the BPM and note settings. In order to interact with the code I implemented a scan matrix of 9 Leds and 9 Buttons on a 6ms loop. I was looking to reduce the need for IO pins and this worked great.

Seperately these two pieces of code work well. Combined however they interfere with each other. When the timer interrupts it advances the current step ( 1-8) and then exits the interrupt. The main body of the code is a switch statement to trigger the wave_player to play the wav file associated with the currently active step. While the wave player is active all other functions cease until the audio is done playing. So you cannot interact with the buttons during this time and the leds that are toggled ON, go OFF until the audio is done .. then they are returned to ON.

I am using the wave_player library in order to play the wavs and they are being streamed from an sd card via the SD_FileSystem library.

My question is this, is there a way to play a wave file and not stop the other functions of the program such as scanning the matrix or listening for analog in changes (pot controls BPM). Or other future features? Is my interrupt based system not correct?

Thanks, Ryan

edit: Inserted code snippet.

void myhandler() 
    {
    // clear the TIMER0 interrupt
    LPC_TIM0->IR = 0x1;
    LPC_TIM0->MCR |= 1 << 1;    // reset on match
    iStep ++; // Advance iStep every interrupt to create the 8 step loop based on bpm and note
    if(iStep >= 8){
        iStep = 0;
    }
    switch ( iStep ) { //reset the step flag on the next step, to ensure its ready to be activated on the next loop
            case 0:
                iFlag7 = 0;
            case 1:
                iFlag0 = 0;                
            case 2:
                iFlag1 = 0;
            case 3:
                iFlag2 = 0;                
            case 4:
                iFlag3 = 0;                
            case 5:
                iFlag4 = 0;
            case 6:
                iFlag5 = 0;
            case 7:
                iFlag6 = 0;
    }
}
    
int main() {
    FILE *wave_file1 = fopen("/sd/wf/909bd_16_22.wav", "r");
    FILE *wave_file4 = fopen("/sd/wf/909rim01_16_22.wav", "r");
    while(1) {
        int changedPins = scanCol(currentCol); // runs the scan matrix function and gets a list of changed button states for the current column
        outputState[currentCol] ^= changedPins; // toggle the LED related to the changed button.
               
        if (LPC_TIM0->TCR == 1) { //if timer started, then play audio per step
            switch ( iStep ) {
                case 0:
                    if ((outputState[1] & 1) == 1 && iFlag0 == 0) { //is a button press detected and is this the first time the audio is played in the current 8 step loop
                        iFlag0 = 1; 
                        fseek(wave_file1, 0, SEEK_SET);  // set file pointer to beginning
                        waver.play(wave_file1);
                    }
                    break;
                case 1:
                    if ((outputState[2] & 1) == 1 && iFlag1 == 0) {
                        iFlag1 = 1;
                        fseek(wave_file4, 0, SEEK_SET);  // set file pointer to beginning
                        waver.play(wave_file4);                         
                    }            
                    break;            
                case 2:
                    if ((outputState[0] & 2) == 2 && iFlag2 == 0) {
                        iFlag2 = 1;
                        fseek(wave_file4, 0, SEEK_SET);  // set file pointer to beginning
                        waver.play(wave_file4);                       
                    }                
                    break;            
                case 3:
                    if ((outputState[1] & 2) == 2 && iFlag3 == 0) {
                        iFlag3 = 1;
                        fseek(wave_file4, 0, SEEK_SET);  // set file pointer to beginning
                        waver.play(wave_file4);
                    }                
                    break;            
                case 4:
                    if ((outputState[2] & 2) == 2 && iFlag4 == 0) {
                        iFlag4 = 1;
                        fseek(wave_file1, 0, SEEK_SET);  // set file pointer to beginning
                        waver.play(wave_file1);
                    }                
                    break;            
                case 5:
                    if ((outputState[0] & 4) == 4 && iFlag5 == 0) {
                        iFlag5 = 1;
                        fseek(wave_file4, 0, SEEK_SET);  // set file pointer to beginning
                        waver.play(wave_file4);                   
                    }                
                    break;            
                case 6:
                    if ((outputState[1] & 4) == 4 && iFlag6 == 0) {
                        iFlag6 =1;
                        fseek(wave_file4, 0, SEEK_SET);  // set file pointer to beginning
                        waver.play(wave_file4);
                    }                
                    break;            
                case 7:
                    if ((outputState[2] & 4) == 4 && iFlag7 == 0) {
                        iFlag7 = 1;
                        fseek(wave_file4, 0, SEEK_SET);  // set file pointer to beginning
                        waver.play(wave_file4);
                    }                
                    break;
            }
        }
        currentCol++; //move to the next cloumn in the matrix
        if (currentCol == numberOfCols) {
            currentCol  = 0;
        }  
    }
}

So while the wav file is playing, your interrupts are not handled? That shouldn't happen, unless it is spending all its time pushing data to the DAC, which does run interrupt based.

posted by Erik - 28 Feb 2016

So far the audio samples I've been playing have been short enough that they dont tend to overlap with the interrupts so I'm not seeing that. I think that might actually happen however if I put longer audio clips on. My timer interrupt simply advances the step count and doesnt actually call the wave player.. The wave player is called during main and the issue is that while the wav file is playing the other code in main (the scan matrix) isn't being executed. I'm currently reading about threading using mbed RTOS, I think this might be the answer.

By the way, I wanted a really precise timer so that the BPM is always accurate, that's why I went with interrupts and a timer.

posted by R T 28 Feb 2016

Ah I expected your button loop to be called by interrupt, but that is also in your main loop?

To give some answer: Yes it is possible for sure. Depending on how much is required and how much target specific things you want to put in it, you can have it just enough to read the buttons, or you can have it run almost completely on the background using DMA. The wave_player lib already uses interrupts to send out data to the DAC. But I don't really have figured out yet how the lib works exactly. Maybe I'll look at it later, but kinda busy coming week at least.

posted by Erik - 28 Feb 2016

Hey eric, I'll just edit the original post to include a snippet of my code.

I've been testing the rtos and I can get the waveplayer to run in a thread while another thread is toggling an led at 1 ms. The audio might be degraded slightly, but I'm not sure.. need to test more audio files. But this seems to be a good option, I'll continue on this.

DMA allows you to run processes uninterrupted in the background? I'll have to look into this too.

posted by R T 28 Feb 2016

RTOS might indeed be a relative easy solution to get it to work reasonably well indeed.

DMA allows memory access to happen without the CPU being involved. For example it can transfer data from the memory to the DAC using a timer. But this is not really supported by mbed, and while there are DMA libs for the LPC1768 here, you still will need to dive quite a bit into the registers of the LPC1768.

posted by Erik - 28 Feb 2016

The DMA sounds interesting, I wonder if it could transfer data from an SD card to the DAC without the cpu. I feel like that won't work since there needs to be SPI involved.

Continuing with RTOS... I tried running the wave player in the main thread and the scan matrix in a secondary thread. There is a noticeable delay unfortunately, I can hear the audio being sliced and see the leds flicker slightly. Is there a way to speed this up? I read that the threads are sliced at 1ms which should be ok or atleast close to ok, but in combination with the code it must be longer between the two threads. If I can just see a flicker in an led it has to be around 10ms I think. It's not a constant period so my scope can't lock onto it.

Edit:

I have noticed that main seems to run quicker than a thread, if I put the scan matrix in a thread I notice a slight flicker in the leds however if I put it in main the led is solid. This is without any additional threads.

posted by R T 28 Feb 2016

I found the problem! My code was using a thread to call the scan matrix function which I ported from a program that I wrote before I knew anything about RTOS.

I decided to modify the scan matrix function to be the thread (instead of a thread calling the function), hoping this would speed up the execution of this code. But the key was that I changed the wait command in the original function to a Thread::wait and it now works great!.

Going to try and implement this in the full code now.

Ryan

posted by R T 29 Feb 2016
Be the first to answer this question.