Store › Forums › Audio Hacker › Discussion and Project Ideas › Adding Filter on Passthrough in Four Sample 12-bit Sampler Project
Tagged: Audio Hacker, Filter, Four Sample 12-bit Sampler
- This topic has 3 replies, 2 voices, and was last updated 6 years, 1 month ago by Michael.
-
AuthorPosts
-
September 30, 2018 at 3:47 pm #9881kreiffParticipant
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;
October 5, 2018 at 1:29 pm #9899MichaelKeymasterSorry 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.
October 5, 2018 at 1:56 pm #9900kreiffParticipantThanks, 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!
October 5, 2018 at 2:13 pm #9901MichaelKeymasterDebugging code only takes a few instruction cycles. It’s the math that is the killer. Filtering computations takes hundreds of cycles.
-
AuthorPosts
- You must be logged in to reply to this topic.