Adding Filter on Passthrough in Four Sample 12-bit Sampler Project

Store Forums Audio Hacker Discussion and Project Ideas Adding Filter on Passthrough in Four Sample 12-bit Sampler Project

This topic contains 3 replies, has 2 voices, and was last updated by  Michael 11 months, 2 weeks ago.

Viewing 4 posts - 1 through 4 (of 4 total)
  • Author
    Posts
  • #9881

    kreiff
    Participant

    Hi All!

    I’m slowly working my way through the Audio Hacker projects and trying to combine them into a kind of DIY SP404.

    I’ve managed to add bit crushing and variable sample rate to the Four Sample 12-bit Sampler Project and I’m in the process of trying to add a Low Pass filter to the project as well. I’ve successfully implemented the filter for sample playback – but I would like to allow the filter to be used on the passthrough signal as well.

    Based on the sample project it was my assumption that the passthrough signal is initiated in the interrupt during this ‘if’ statement:

    
    if ((mode != PLAYBACK) && (mode != RECORD_DONE)) {
        // Read ADC
        signal = AudioHacker.readADC();
        
      }
    

    So, I’ve tried to implement the filter on the passthrough signal this way:

    
    if ((mode != PLAYBACK) && (mode != RECORD_DONE)) {
        // Read ADC
        signal = AudioHacker.readADC();
        int highPass = signal - buf0;
        int bandPass = buf0 - buf1;
        int tmp = highPass + (feedback * bandPass >> 8);
        buf0 += ((long)filterCutoff * tmp) >> 8;
        buf1 += ((long)filterCutoff * (buf0 - buf1)) >> 8;
        if(filterType == LOW_PASS){
        signal = buf1;    
      }
    }
    

    Unfortunately, this completely breaks the program – I get no passthrough and no samples.

    Below is the complete code I have so far – Any ideas about how I could implement filtering on the passthrough?:

    
    #include <EEPROM.h>
    #include <AudioHacker.h>
    
    #define DEBUG
    #define OFF 0
    #define PASSTHROUGH 1
    #define RECORD 2
    #define PLAYBACK 3
    #define RECORD_DONE 4
    #define RECORD_BUTTON 5
    #define SAMPLE0_BUTTON 6
    #define SAMPLE1_BUTTON 4
    #define SAMPLE2_BUTTON 3
    #define SAMPLE3_BUTTON 2
    #define LOW_PASS 7
    #define BAND_PASS 8
    #define HIGH_PASS 9
    
    unsigned int playbackBuf = 2048;
    unsigned int passthroughSampleRate;
    unsigned int recordingSampleRate;
    unsigned int playbackSampleRate;
    byte resolution = 12;
    unsigned int mask;
    unsigned int timer1Start;
    volatile unsigned int timer1EndEven;
    volatile unsigned int timer1EndOdd;
    volatile boolean warning = false;
    boolean adjustablePlaybackSpeed = true;  // set to true with pot connected to A0
    int currentA0Position;
    volatile long address = 0;
    volatile long endAddress[4];
    volatile byte addressChipNumber = 0;
    
    boolean on = true;
    int filterCutoff = 255;
    int filterResonance = 255;
    long feedback;
    int buf0 = 0;
    int buf1 = 0;
    byte filterType = LOW_PASS;
    
    volatile byte mode = PASSTHROUGH;
    unsigned long lastDebugPrint = 0;
    unsigned int readBuf[2];
    unsigned int writeBuf;
    boolean evenCycle = true;
    
    // set to true if you are using a battery backup on the
    // SRAM and want to keep sample address info in EEPROM
    boolean batteryBackup = true;
    
    unsigned int recordStartTime;
    unsigned int recordEndTime;
    boolean sampleRecorded[4];
    byte sample;
    
    void setup() {
    #ifdef DEBUG
      Serial.begin(115200);        // connect to the serial port
    #endif
    
      recordingSampleRate = DEFAULT_RECORDING_SAMPLE_RATE;
      passthroughSampleRate = DEFAULT_SAMPLE_RATE;
      timer1Start = UINT16_MAX - (F_CPU / passthroughSampleRate);
    
      pinMode(RECORD_BUTTON, INPUT);
      pinMode(SAMPLE0_BUTTON, INPUT);
      pinMode(SAMPLE1_BUTTON, INPUT);
      pinMode(SAMPLE2_BUTTON, INPUT);
      pinMode(SAMPLE3_BUTTON, INPUT);
    
      digitalWrite(RECORD_BUTTON, HIGH);
      digitalWrite(SAMPLE0_BUTTON, HIGH);
      digitalWrite(SAMPLE1_BUTTON, HIGH);
      digitalWrite(SAMPLE2_BUTTON, HIGH);
      digitalWrite(SAMPLE3_BUTTON, HIGH);
    
      AudioHacker.begin();
    
    #ifdef DEBUG
      Serial.print("sample rate = ");
      Serial.print(passthroughSampleRate);
      Serial.print(" Hz, recording sample rate = ");
      Serial.print(recordingSampleRate);
      Serial.print(" Hz");
      Serial.println();
    #endif
    
      sampleRecorded[0] = false;
      sampleRecorded[1] = false;
      sampleRecorded[2] = false;
      sampleRecorded[3] = false;
    
      if (batteryBackup) {
        // Read endAddress[] values from EEPROM when we have a battery
        // connected to the Audio Hacker to preserve SRAM contents.
        for (byte i=0;i<4;i++) {
          byte a = i*3;
          long b;
          b = (long)EEPROM.read(a);
          endAddress[i] = (b << 16);
          b = (long)EEPROM.read(a+1);
          endAddress[i] |= (b << 8);
          b = (long)EEPROM.read(a+2);
          endAddress[i] |= b;
    
          if (endAddress[i] > 0) {
    	       sampleRecorded[i] = true;
    #ifdef DEBUG
    	       Serial.print("sample ");
    	       Serial.print(i);
    	       Serial.print(" endAddress = ");
    	       Serial.print(endAddress[i]);
    	       Serial.println();
    #endif
          }
        }
      }
    }
    
    void loop() {
    
      resolution = map(analogRead(1), 0, 1023, 1, 12);
      mask = 0x0FFF << (12-resolution);
    
      filterCutoff = analogRead(2) >> 2;
      filterResonance = analogRead(3) >> 2;
    
      feedback = (long)filterResonance + (long)(((long)filterResonance * ((int)255 - (255-filterCutoff))) >> 8);
    
    #ifdef DEBUG
      if ((millis() - lastDebugPrint) >= 1000) {
        lastDebugPrint = millis();
    
        // Print the number of instruction cycles remaining at the end of the ISR.
        // The more work you try to do in the ISR, the lower this number will become.
        // If the number of cycles remaining reaches 0, then the ISR will take up
        // all the CPU time and the code in loop() will not run.
    
        /*
        Serial.print("even cycles remaining = ");
        Serial.print(UINT16_MAX - timer1EndEven);
        Serial.print("   odd cycles remaining = ");
        Serial.print(UINT16_MAX - timer1EndOdd);
        Serial.println();
        if (((UINT16_MAX - timer1EndEven) < 20) || (((UINT16_MAX - timer1EndOdd) < 20))) {
          Serial.println("WARNING: ISR execution time is too long. Reduce sample rate or reduce the amount of code in the ISR.");
        }
        */
      }
    #endif
    
      if ((mode == OFF) || (mode == PASSTHROUGH)) {
        if ((digitalRead(RECORD_BUTTON) == LOW) && ((digitalRead(SAMPLE0_BUTTON) == LOW) || (digitalRead(SAMPLE1_BUTTON) == LOW) || (digitalRead(SAMPLE2_BUTTON) == LOW) || (digitalRead(SAMPLE3_BUTTON) == LOW))) {
          // enter RECORD mode
          recordStartTime = millis();
          if ((recordStartTime - recordEndTime) < 20) {
            // debounce the record button.
            recordStartTime = 0;
            return;
          }
          if (digitalRead(SAMPLE0_BUTTON) == LOW) {
            sample = 0;
            address = 0;
            addressChipNumber = 0;
          }
          if (digitalRead(SAMPLE1_BUTTON) == LOW) {
            sample = 1;
            address = 65535;
            addressChipNumber = 0;
          }
          if (digitalRead(SAMPLE2_BUTTON) == LOW) {
            sample = 2;
            address = 0;
            addressChipNumber = 1;
          }
          if (digitalRead(SAMPLE3_BUTTON) == LOW) {
            sample = 3;
            address = 65535;
            addressChipNumber = 1;
          }
          mode = RECORD;
          timer1Start = UINT16_MAX - (F_CPU / recordingSampleRate);
          currentA0Position = analogRead(0);
        } else {
          // enter PLAYBACK mode
          if ((digitalRead(SAMPLE0_BUTTON) == LOW) && (sampleRecorded[0])) {
            address = 0;
            addressChipNumber = 0;
            sample = 0;
            mode = PLAYBACK;
          }
          if ((digitalRead(SAMPLE1_BUTTON) == LOW) && (sampleRecorded[1])) {
            address = 65535;
            addressChipNumber = 0;
            sample = 1;
            mode = PLAYBACK;
          }
          if ((digitalRead(SAMPLE2_BUTTON) == LOW) && (sampleRecorded[2])) {
            address = 0;
            addressChipNumber = 1;
            sample = 2;
            mode = PLAYBACK;
          }
          if ((digitalRead(SAMPLE3_BUTTON) == LOW) && (sampleRecorded[3])) {
            address = 65535;
            addressChipNumber = 1;
            sample = 3;
            mode = PLAYBACK;
          }
        }
      }
    
      if (mode == PASSTHROUGH) {
        timer1Start = UINT16_MAX - (F_CPU / passthroughSampleRate); 
      }
    
      if (mode == RECORD) {
        if (((sample == 0) && (digitalRead(SAMPLE0_BUTTON) == HIGH)) ||
            ((sample == 1) && (digitalRead(SAMPLE1_BUTTON) == HIGH)) ||
            ((sample == 2) && (digitalRead(SAMPLE2_BUTTON) == HIGH)) ||
            ((sample == 3) && (digitalRead(SAMPLE3_BUTTON) == HIGH))) {
              // recording stopped
              recordEndTime = millis();
              if (recordEndTime - recordStartTime < 20) {
                // debounce
                return;
              }
              sampleRecorded[sample] = true;
              endAddress[sample] = address;
    #ifdef DEBUG
              Serial.print("sample ");
              Serial.print(sample);
              Serial.print(" recording time = ");
              Serial.print(recordEndTime - recordStartTime);
              Serial.println(" ms");
              Serial.print(" endAddress = ");
              Serial.println(endAddress[sample]);
    #endif
    
              if (batteryBackup) {
                // Write endAddress to EEPROM for battery backup use.
                byte a = sample*3;
                EEPROM.write(a, (endAddress[sample] >> 16) & 0xFF);
                EEPROM.write(a+1, (endAddress[sample] >> 8) & 0xFF);
                EEPROM.write(a+2, endAddress[sample] & 0xFF);
              }
              mode = PASSTHROUGH;
            }
      } else {
      }
    
      if (mode == RECORD_DONE) {
        if (recordStartTime != 0) {
    #ifdef DEBUG
          Serial.print("sample ");
          Serial.print(sample);
          Serial.print(" recording time = ");
          Serial.print(millis() - recordStartTime);
          Serial.println(" ms");
          Serial.print(" endAddress = ");
          Serial.println(endAddress[sample]);
    #endif
          sampleRecorded[sample] = true;
          recordStartTime = 0;
    
          if (batteryBackup) {
            // Write endAddress to EEPROM for battery backup use.
            byte a = sample*3;
            EEPROM.write(a, (endAddress[sample] >> 16) & 0xFF);
            EEPROM.write(a+1, (endAddress[sample] >> 8) & 0xFF);
            EEPROM.write(a+2, endAddress[sample] & 0xFF);
          }
        }
        if (digitalRead(RECORD_BUTTON) == HIGH) {
          // record button released
          mode = PASSTHROUGH;
        }
      }
    
      if (mode == PLAYBACK) {
        digitalWrite(A5, HIGH);
        if (((sample == 0) && (digitalRead(SAMPLE0_BUTTON) == HIGH)) ||
            ((sample == 1) && (digitalRead(SAMPLE1_BUTTON) == HIGH)) ||
            ((sample == 2) && (digitalRead(SAMPLE2_BUTTON) == HIGH)) ||
            ((sample == 3) && (digitalRead(SAMPLE3_BUTTON) == HIGH))) {
              // play button released
              mode = PASSTHROUGH;
        } else {
          if (adjustablePlaybackSpeed) {
            playbackSampleRate = map(currentA0Position-analogRead(0), -1023, 1023, recordingSampleRate+20000, recordingSampleRate-20000);
          } else {
            playbackSampleRate = recordingSampleRate;
          }
          // compute the start value for counter1 to achieve the chosen playback rate
          timer1Start = UINT16_MAX - (F_CPU / playbackSampleRate);
        }
      }
    }
    
    ISR(TIMER1_OVF_vect) {
      TCNT1 = timer1Start;
      unsigned int signal;
    
      if (mode != RECORD_DONE) {
        AudioHacker.writeDAC(playbackBuf);
      }
    
      if ((mode != PLAYBACK) && (mode != RECORD_DONE)) {
        // Read ADC
        signal = AudioHacker.readADC();
        
      }
    
      if (mode == RECORD) {
        if (evenCycle) {
          // we only write to memory on odd cycles, so buffer the sampled signal.
          writeBuf = signal;
        } else {
          // Write to SRAM
          AudioHacker.writeSRAMPacked(addressChipNumber, address, writeBuf, signal);
    
          address += 3;
          if (((sample == 0) && (address > 65532)) ||
              ((sample == 1) && (address > MAX_ADDR)) ||
              ((sample == 2) && (address > 65532)) ||
              ((sample == 3) && (address > MAX_ADDR))) {
                // end of memory, stop recording
                mode = RECORD_DONE;
                endAddress[sample] = address;
          }
        }
      }
    
      if (mode == PLAYBACK) {
        if (evenCycle) {
          // Read from SRAM
          AudioHacker.readSRAMPacked(addressChipNumber, address, readBuf);
          signal = readBuf[0];
    
          int highPass = signal - buf0;
          int bandPass = buf0 - buf1;
          int tmp = highPass + (feedback * bandPass >> 8);
          buf0 += ((long)filterCutoff * tmp) >> 8;
          buf1 += ((long)filterCutoff * (buf0 - buf1)) >> 8;
          if(filterType == LOW_PASS){
          signal = buf1;
          } 
    
          address += 3;
          if ((sample == 0) && (address == endAddress[0])) {
            address = 0;
            addressChipNumber = 0;
          }
          if ((sample == 1) && (address == endAddress[1])) {
            address = 65535;
            addressChipNumber = 0;
          }
          if ((sample == 2) && (address == endAddress[2])) {
            address = 0;
            addressChipNumber = 1;
          }
          if ((sample == 3) && (address == endAddress[3])) {
            address = 65535;
            addressChipNumber = 1;
          }
        } else {
          signal = readBuf[1];
    
          int highPass = signal - buf0;
          int bandPass = buf0 - buf1;
          int tmp = highPass + (feedback * bandPass >> 8);
          buf0 += ((long)filterCutoff * tmp) >> 8;
          buf1 += ((long)filterCutoff * (buf0 - buf1)) >> 8;
          if(filterType == LOW_PASS){
          signal = buf1;
          }
        }
      } // PLAYBACK
      
      playbackBuf = signal;
      playbackBuf &= mask;
    
    #ifdef DEBUG
      if (evenCycle) {
        timer1EndEven = TCNT1;
      } else {
        timer1EndOdd = TCNT1;
      }
    #endif
      evenCycle = !evenCycle;
    
    #9899

    Michael
    Keymaster

    Sorry for the slow response — you are doing a good job so far combining various effects into one sketch. Timing is very critical on all this, and that’s why some tasks happen only on ‘odd’ cycles through the ISR and some happen on the ‘even’ cycles. Filtering takes a long time, and what might be happening is that you are running out of time when performing the filtering logic right after reading the ADC. One thing to try is to reduce the sample rate which will give each trip through the ISR more time to perform the computation. It looks like you have the sample rate for passthrough set to the default which is 44100. Normal passthrough works at this rate, but filtering won’t. Lower it to 22050 or even 16000.

    Now that I look at the Filter example, it uses a sample rate of 22050. Your code above is trying to do filtering with passthrough rate of 44100.

    #9900

    kreiff
    Participant

    Thanks, Michael!!

    Glad to know that I was on the right track. I hadn’t even considered that the sample rate would prevent the interrupt code from executing. That makes sense, though! I’ll have to be more conscious of execution times as I add additional features.

    Will I get some more time back by removing some of the debugging code, too? Might have a go at that next as well.

    Thanks again!

    #9901

    Michael
    Keymaster

    Debugging code only takes a few instruction cycles. It’s the math that is the killer. Filtering computations takes hundreds of cycles.

Viewing 4 posts - 1 through 4 (of 4 total)

You must be logged in to reply to this topic.