Audio filter

Status
Not open for further replies.

DaniZ

Member
Hi, The example file ‘Filter’ (under Audio) operates well on my teensy 4.1 and Audio Adaptor plugged on it making use of the biquad filter as a band pass filter (BPF) with the parameter ‘stage’ equals zero. I was not able to operate the example // marked options of biquad with stage = 1, 2, or 3. Does anyone knows how these biquad stage values can be used?

My interest is in constructing a audio BPF with manually controlled center frequency (200 to 4 kHz) and controlled band width (200 to 3 kHz). Does anyone knows of a Sketch example that can support that realization?
 
Here is a fairly long example using biquads that I think has what you want. It uses multistage biquads.

Code:
/* 
-Sept 23 '19  Added code to modify the  MODULATION_PITCH_COEFFICIENT (initial and secondary) to reflect the value of the MOD Wheel (Continuous Controller #1)
- modified the "synthWavetable.h and .cpp files to handle these changing variables in real-time (during update routine)
*/
#include <SD.h>
#include <Audio.h>
#include <MIDI.h>
#include <LiquidCrystal.h> 
#include <Encoder.h>


const int TOTAL_VOICES = 48;
const int TOTAL_MIXERS = 17;  // + 3 for the Graphic EQ

AudioSynthWavetable wavetable[TOTAL_VOICES];
AudioMixer4 mixer[TOTAL_MIXERS];
AudioOutputPT8211 pt8211_1;
AudioFilterBiquad        filter1;        
AudioFilterBiquad        filter2;       
AudioFilterBiquad        filter3;        
AudioFilterBiquad        filter4;        
AudioFilterBiquad        filter5;       
AudioFilterBiquad        filter6;       
AudioFilterBiquad        filter7;       
AudioMixer4 mixer1;
AudioMixer4 mixer2;
AudioMixer4 mixer3;
AudioConnection patchCord[] = {
	{ wavetable[0], 0, mixer[0], 0 },{ wavetable[1], 0, mixer[0], 1 },{ wavetable[2], 0, mixer[0],  2 },{ wavetable[3], 0, mixer[0],  3 },{ mixer[0], 0, mixer[TOTAL_MIXERS - 2], 0 },
	{ wavetable[4], 0, mixer[1], 0 },{ wavetable[5], 0, mixer[1], 1 },{ wavetable[6], 0, mixer[1],  2 },{ wavetable[7], 0, mixer[1],  3 },{ mixer[1], 0, mixer[TOTAL_MIXERS - 2], 1 },
	{ wavetable[8], 0, mixer[2], 0 },{ wavetable[9], 0, mixer[2], 1 },{ wavetable[10], 0, mixer[2],  2 },{ wavetable[11], 0, mixer[2],  3 },{ mixer[2], 0, mixer[TOTAL_MIXERS - 2], 2 },
	{ wavetable[12], 0, mixer[3], 0 },{ wavetable[13], 0, mixer[3], 1 },{ wavetable[14], 0, mixer[3],  2 },{ wavetable[15], 0, mixer[3],  3 },{ mixer[3], 0, mixer[TOTAL_MIXERS - 2], 3 },
	{ wavetable[16], 0, mixer[4], 0 },{ wavetable[17], 0, mixer[4], 1 },{ wavetable[18], 0, mixer[4],  2 },{ wavetable[19], 0, mixer[4],  3 },{ mixer[4], 0, mixer[TOTAL_MIXERS - 3], 0 },
	{ wavetable[20], 0, mixer[5], 0 },{ wavetable[21], 0, mixer[5], 1 },{ wavetable[22], 0, mixer[5],  2 },{ wavetable[23], 0, mixer[5],  3 },{ mixer[5], 0, mixer[TOTAL_MIXERS - 3], 1 },
	{ wavetable[24], 0, mixer[6], 0 },{ wavetable[25], 0, mixer[6], 1 },{ wavetable[26], 0, mixer[6],  2 },{ wavetable[27], 0, mixer[6],  3 },{ mixer[6], 0, mixer[TOTAL_MIXERS - 3], 2 },
	{ wavetable[28], 0, mixer[7], 0 },{ wavetable[29], 0, mixer[7], 1 },{ wavetable[30], 0, mixer[7],  2 },{ wavetable[31], 0, mixer[7],  3 },{ mixer[7], 0, mixer[TOTAL_MIXERS - 3], 3 },
	{ wavetable[32], 0, mixer[8], 0 },{ wavetable[33], 0, mixer[8], 1 },{ wavetable[34], 0, mixer[8],  2 },{ wavetable[35], 0, mixer[8],  3 },{ mixer[8], 0, mixer[TOTAL_MIXERS - 4], 0 },
	{ wavetable[36], 0, mixer[9], 0 },{ wavetable[37], 0, mixer[9], 1 },{ wavetable[38], 0, mixer[9],  2 },{ wavetable[39], 0, mixer[9],  3 },{ mixer[9], 0, mixer[TOTAL_MIXERS - 4], 1 },
	{ wavetable[40], 0, mixer[10], 0 },{ wavetable[41], 0, mixer[10], 1 },{ wavetable[42], 0, mixer[10], 2 },{ wavetable[43], 0, mixer[10], 3 },{ mixer[10], 0, mixer[TOTAL_MIXERS - 4], 2 },
	{ wavetable[44], 0, mixer[11], 0 },{ wavetable[45], 0, mixer[11], 1 },{ wavetable[46], 0, mixer[11], 2 },{ wavetable[47], 0, mixer[11], 3 },{ mixer[11], 0, mixer[TOTAL_MIXERS - 4], 3 },
	{ mixer[TOTAL_MIXERS - 2], 0, mixer[TOTAL_MIXERS - 1], 0 },
	{ mixer[TOTAL_MIXERS - 3], 0, mixer[TOTAL_MIXERS - 1], 1 },
	{ mixer[TOTAL_MIXERS - 4], 0, mixer[TOTAL_MIXERS - 1], 2 },
	{ mixer[TOTAL_MIXERS - 5], 0, mixer[TOTAL_MIXERS - 1], 3 },
	};

AudioConnection          patchCord1(mixer[TOTAL_MIXERS - 1], 0, filter1, 0);  // Graphic EQ band 1
AudioConnection          patchCord2(mixer[TOTAL_MIXERS - 1], 0, filter2, 0);  // Graphic EQ band 2
AudioConnection          patchCord3(mixer[TOTAL_MIXERS - 1], 0, filter3, 0);  // 
AudioConnection          patchCord4(mixer[TOTAL_MIXERS - 1], 0, filter4, 0);  //
AudioConnection          patchCord5(mixer[TOTAL_MIXERS - 1], 0, filter5, 0);  //
AudioConnection          patchCord6(mixer[TOTAL_MIXERS - 1], 0, filter6, 0);  //
AudioConnection          patchCord7(mixer[TOTAL_MIXERS - 1], 0, filter7, 0);  // Graphic EQ band 7


AudioConnection          patchCord8(filter1, 0, mixer1, 0); 
AudioConnection          patchCord9(filter2, 0, mixer1,1);  
AudioConnection          patchCord10(filter3, 0, mixer1, 2); 
AudioConnection          patchCord11(filter4, 0, mixer1, 3); 
AudioConnection          patchCord12(filter5, 0, mixer2, 0); 
AudioConnection          patchCord13(filter6, 0, mixer2, 1); 
AudioConnection          patchCord14(filter7, 0, mixer2, 2);

AudioConnection          patchCord15(mixer1, 0, mixer3, 0); 
AudioConnection          patchCord16(mixer2, 0, mixer3, 1);
AudioConnection          patchCord17(mixer3, 0, pt8211_1, 0);
AudioConnection          patchCord18(mixer3, 0, pt8211_1, 1);


MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

// configure the character LCD object
// Since I am using GPIO1, which gets configured by the MIDI object (as MIDI OUT), must do this LCD display configuration AFTER MIDI port is configured

const int rs = 1, en = 2, d4 = 3, d5 = 4, d6 = 5, d7 = 6;  // define the port pins used for the 6 LCD signals
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

File myFile;

Encoder REnc(16, 17);  // set up a rotary encoder on GPIO16,17


int notes_played = 0;
int noteAlloc[TOTAL_VOICES];
String tmpString;
String paramString;
String voiceName;
int totalSampleLength = 0;
int numRanges;
int sIndex;
int fIndex;
int rangeIndex;
int err;
String paramString2;
bool voiceLoading = false;
byte programNumber = 1;
byte controlNumber = 0;
byte controlValue = 0;
int32_t RotaryEncoderValue = 0;
int numPrograms = 0;
int cents_offset;
float sample_rate;
int sample_note;
int menuItem=0;
volatile byte currentControl;
volatile byte currentControlValue;
bool learnMode = false;

struct config_Array {
	byte ID;
	byte Control_Translation_Map[16];
	byte filter1_value[127];  
	byte filter2_value[127];
	byte filter3_value[127];
	byte filter4_value[127];
	byte filter5_value[127];
	byte filter6_value[127];
	byte filter7_value[127];
	byte MIDI_channel;
	};

config_Array cfg;

struct sample_data {
	int16_t* sample;
	bool LOOP;
	int INDEX_BITS;
	float PER_HERTZ_PHASE_INCREMENT;
	uint32_t MAX_PHASE;
	uint32_t LOOP_PHASE_END;
	uint32_t LOOP_PHASE_LENGTH;
	uint16_t INITIAL_ATTENUATION_SCALAR;
	// VOLUME ENVELOPE VALUES
	uint32_t DELAY_COUNT;
	uint32_t ATTACK_COUNT;
	uint32_t HOLD_COUNT;
	uint32_t DECAY_COUNT;
	uint32_t RELEASE_COUNT;
	int32_t SUSTAIN_MULT;
	// VIBRATO VALUES
	uint32_t VIBRATO_DELAY;
	uint32_t VIBRATO_INCREMENT;
	float VIBRATO_PITCH_COEFFICIENT_INITIAL;
	float VIBRATO_PITCH_COEFFICIENT_SECOND;
	// MODULATION VALUES
	uint32_t MODULATION_DELAY;
	uint32_t MODULATION_INCREMENT;
	float MODULATION_PITCH_COEFFICIENT_INITIAL;
	float MODULATION_PITCH_COEFFICIENT_SECOND;
	int32_t MODULATION_AMPLITUDE_INITIAL_GAIN;
	int32_t MODULATION_AMPLITUDE_SECOND_GAIN;
};

AudioSynthWavetable::sample_data sd0[10];

#define MENU 9  // menu on GPIO9
#define ENTER 10   // Enter on GPIO10
#define SAVE 11   // SAVE on GPIO11
#define EXIT 12   // EXIT on GPIO12
#define LED	 22   // Key pressed LED
#define MAX_MENU_ITEMS 2
#define MAX_RANGES 10

// following are constants that were defined in the AudioSynthWavetable library, and are used in main program for some calculations

#define CENTS_SHIFT(C) (pow(2.0, (C)/1200.0))
#define NOTE_TO_FREQUENCY(N) (440.0 * pow(2.0, ((N) - 69) / 12.0))
#define NOTE(N) (440.0 * pow(2.0, ((N) - 69) / 12.0))
#define DECIBEL_SHIFT(dB) (pow(10.0, (dB)/20.0))
const int32_t UNITY_GAIN = INT32_MAX;
const int32_t LFO_SMOOTHNESS = 3;
const float LFO_PERIOD = (AUDIO_BLOCK_SAMPLES / (1 << (LFO_SMOOTHNESS - 1)));
static  float SAMPLES_PER_MSEC = (AUDIO_SAMPLE_RATE_EXACT / 1000.0);
static const int32_t ENVELOPE_PERIOD = 8;


#define MAX_WAVE_SAMPLES 120000  // usually limited to about 95000, but can go to 120000 or so if DMAMEM is specified in the array declaration (otherwise it has to share 512K mem with program FLASH
uint32_t DMAMEM  SAMPLE_BUFFER[MAX_WAVE_SAMPLES];  // This stores the actual waveform data- broken into up to MAX_RANGES regions
uint8_t DefaultVoice_ranges[MAX_RANGES];  

AudioSynthWavetable::instrument_data DefaultVoice = { numRanges, DefaultVoice_ranges, sd0 };  

void setup() {
	pinMode(MENU, INPUT_PULLUP);
	pinMode(ENTER, INPUT_PULLUP);
	pinMode(SAVE, INPUT_PULLUP);
	pinMode(EXIT, INPUT_PULLUP);
	pinMode(LED, OUTPUT);
	digitalWrite(LED, LOW);
	Serial.begin(115200);
	delay(1000);
	Serial.println("starting up");
	MIDI.begin(MIDI_CHANNEL_OMNI);
	MIDI.turnThruOff();  // this is supposed to stop MIDI IN being echoed to the Tx port associated with that MIDI port.
	// however, still interferes with the LCD display when it is using GPIO1, unless you configure the LCD display after using the MIDI.begin() command.
	lcd.begin(20, 2);
	// Print a message to the LCD.
	lcd.print("SoundFont");
	lcd.setCursor(0, 2);
	lcd.print("Synthesizer  ");
	delay(2000);
	checkSDCard();
	//initConfigFile();
	loadConfigFile();
	lcd.clear();
	lcd.print("Config parameters");
	lcd.setCursor(0, 1);
	lcd.print("loaded");
	delay(1000);
	MIDI.setHandleNoteOn(handleNoteOn);
	MIDI.setHandleNoteOff(handleNoteOff);
	MIDI.setHandleProgramChange(handleProgramChange);
	MIDI.setHandleControlChange(handleControlChange);
	MIDI.setHandlePitchBend(handlePitchBendChange);
	MIDI.setHandleAfterTouchChannel(handleAfterTouchChannel);
	AudioMemory(120);  // estimate 120 blocks needed as there are 48 total voices and 20 mixers and 7 filters plus some extras for unknowns 
	// de-allocate all 48 voices
	for (int i = 0; i < TOTAL_VOICES; i++) {
		noteAlloc[i] = 0;
	}
	// setup all mixer gains
	for (int i = 0; i < TOTAL_MIXERS - 3; i++) {
		for (int j = 0; j < 4; j++) {
			mixer[i].gain(j, 0.50);
		}
	}
	for (int j = 0; j < 4; j++) {
		mixer[TOTAL_MIXERS - 4].gain(j, 1);
		mixer[TOTAL_MIXERS - 3].gain(j, 1);
		mixer[TOTAL_MIXERS - 2].gain(j, 1);
	}
	mixer[TOTAL_MIXERS - 1].gain(0, 1);
	mixer[TOTAL_MIXERS - 1].gain(1, 1);


	for (int i = 0; i < TOTAL_VOICES; i++) {
		wavetable[i].setInstrument(DefaultVoice);  
		wavetable[i].amplitude(1);
	}
	// setup default 7 band graphic EQ filter Freq and Q parameters 
	setFilters();
	
	// setup default 7 band graphic EQ gains via mixers
	mixer1.gain(0, 1);  // 
	mixer1.gain(1, 1);  // 
	mixer1.gain(2, 1);  // 
	mixer1.gain(3, 1);  // 
	mixer2.gain(0, 1);  // 
	mixer2.gain(1, 1);  //
	mixer2.gain(2, 1); //
	mixer3.gain(0, 1); // Final mix of filters 1-4
	mixer3.gain(1, 1); // final mix of filter5-7 

	
	err=loadVoice(programNumber);
	if (err == -1) {
		lcd.clear();
		lcd.print("SD Card error : ");
		lcd.setCursor(0, 1);
		lcd.print("voice file not found");
	}
	REnc.write(programNumber);  // RE can change programs while in playing mode
	RotaryEncoderValue = programNumber;
}
 

void loop() {
	MIDI.read();
	freeVoices();
	int32_t REValue = REnc.read();
	if (programNumber != REValue) {
				if (REValue < 1) {
					REValue = 1;
				REnc.write(REValue);
			}
			if (REValue > 127) {
				REValue = 127;
				REnc.write(REValue);
			}
			programNumber = REValue;
			loadVoice(programNumber);
	}
	if (digitalRead(MENU) == 0) {
		menu();
	}
	if (digitalRead(SAVE) == 0) {
		saveConfigFile();
		while (digitalRead(SAVE) == 0);;;
	}
}

int loadVoice(int number) {
	char buffer[100];
	err = -1;
	voiceLoading = true;
	lcd.clear();
	lcd.print("Program # ");
	lcd.print(number);
	lcd.setCursor(0, 1); // second row
	lcd.print("Empty");
	char fname[20];
	String filename;
	String fn= String(number);
	filename = fn + ".cpp";
	filename.toCharArray(fname, 20);
	Serial.println(fname);

	myFile = SD.open(fname);
	if (myFile) {
		err = 0;
		Serial.println("cpp file opened");
		int i = 0;
		while (myFile.available()) {
			myFile.readBytesUntil(',', buffer, sizeof(buffer));
			if ((i % 3000) == 0) {
				lcd.print(".");
			}
			uint32_t value = strtoul(buffer, 0, 16);
			SAMPLE_BUFFER[i] = value;
			i++;
			if (i == MAX_WAVE_SAMPLES) {
				lcd.clear();
				lcd.print("Too many wave samples");
				lcd.setCursor(0, 1);
				lcd.print("Truncated");
				delay(2000);
				break;
			}
		}
		// close the file:
		myFile.close();
		totalSampleLength = i - 1;
		Serial.println();
		Serial.print("num samples");
		Serial.println(totalSampleLength);

	}
	if (err == -1) {
		voiceLoading = false;
		return err;
	}
	filename = fn + ".h";
	filename.toCharArray(fname, 20);
	Serial.println(fname);
	Serial.println(fname);
	myFile = SD.open(fname);
	if (myFile) {
		err = 0;
		rangeIndex = 0;
		Serial.println("h file opened");
		int i = 0;
		int l;
		lcd.print(".");  // progress indicator
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));
		buffer[l] = 0;
		String paramString1(buffer);

		int index = paramString1.indexOf("[", 0);
		index++;
		for (i = 0; i < 10; i++) {
			buffer[i] = 0;
		}
		for (i = 0; i < 3; i++) {
			char t = paramString1.charAt(index + i);
			if (t == ']') {
				buffer[i] = 0;
			}
			else {
				buffer[i] = t;
			}
		}
		String tmpString(buffer);
		numRanges = tmpString.toInt();
		Serial.print("Number of ranges= ");
		Serial.println(numRanges);
		if (numRanges > MAX_RANGES) {
		Serial.println("Limited to MAX_RANGES value of 8");
		numRanges = MAX_RANGES;
		}
		// get upper key values for each range from following line
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));
		String paramString2(buffer);
		Serial.println(paramString2);
		// first isolate the name for display purposes
		fIndex = paramString2.indexOf("_", 0);
		voiceName = paramString2.substring(0, fIndex);
		sIndex = paramString2.indexOf("=", 0);
		sIndex++;
		fIndex = paramString2.indexOf(",", 0);
		String tmpString2 = paramString2.substring(sIndex, fIndex);
		DefaultVoice_ranges[0] = tmpString2.toInt();
		Serial.println(DefaultVoice_ranges[0]);
		// get the rest of the ranges
		for (int i = 1; i < numRanges; i++) {
			sIndex = fIndex + 1;
			fIndex = paramString2.indexOf(",", sIndex);
			String tmpString3 = paramString2.substring(sIndex, fIndex);
			DefaultVoice_ranges[i] = tmpString3.toInt();
			Serial.println(DefaultVoice_ranges[i]);
		}
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // region name
		Serial.println(buffer);

		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get loop true/false
		String paramString3(buffer);
		sIndex = paramString3.indexOf("true", 0);
		if (sIndex >= 0) {
			sd0[0].LOOP = true;
			Serial.println("true");
		}
		else {
			sd0[0].LOOP = false;
			Serial.println("false");
		}
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get INDEX_BITS
		String paramString4(buffer);
		sd0[0].INDEX_BITS = paramString4.toInt();
		Serial.println(sd0[0].INDEX_BITS);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get cents_offset
		String paramString5(buffer);
		cents_offset = paramString5.toInt();
		Serial.println(cents_offset);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get sample_rate
		String paramString6(buffer);
		sample_rate = paramString6.toFloat();
		Serial.println(sample_rate);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get sample_note
		String paramString7(buffer);
		sample_note = paramString7.toFloat();
		Serial.println(sample_note);
		// calculate PER_HERTZ_PHASE_INCREMENT
		sd0[0].PER_HERTZ_PHASE_INCREMENT = (1 << (32 - sd0[0].INDEX_BITS)) * CENTS_SHIFT(cents_offset) * sample_rate / NOTE(sample_note) / AUDIO_SAMPLE_RATE_EXACT + 0.5;
		Serial.println(sd0[0].PER_HERTZ_PHASE_INCREMENT);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get MAX_PHASE
		String paramString8(buffer);
		int temp1 = paramString8.toInt();
		Serial.println(temp1);

		sd0[0].MAX_PHASE = ((uint32_t)temp1 - 1) << (32 - sd0[0].INDEX_BITS);
		Serial.println(sd0[0].MAX_PHASE);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get LOOP_PHASE_END
		String paramString9(buffer);
		temp1 = paramString9.toInt();
		int tempT = temp1;
		Serial.println(temp1);
		sd0[0].LOOP_PHASE_END = ((uint32_t)temp1 - 1) << (32 - sd0[0].INDEX_BITS);
		Serial.println(sd0[0].LOOP_PHASE_END);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get LOOP_PHASE_LENGTH
		String paramString10(buffer);
		temp1 = paramString10.toInt();
		Serial.println(temp1);
		sd0[0].LOOP_PHASE_LENGTH = ((((uint32_t)tempT - 1) << (32 - sd0[0].INDEX_BITS)) - (((uint32_t)temp1 - 1) << (32 - sd0[0].INDEX_BITS))); // LOOP_PHASE_LENGTH		// ((uint32_t)temp1 - 1) << (32 - sd0[0].INDEX_BITS);
		Serial.println(sd0[0].LOOP_PHASE_LENGTH);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get INITIAL_ATTENUATION_SCALAR
		String paramString11(buffer);
		temp1 = paramString11.toInt();
		sd0[0].INITIAL_ATTENUATION_SCALAR = (uint16_t)UINT16_MAX * DECIBEL_SHIFT(temp1);
		Serial.println(sd0[0].INITIAL_ATTENUATION_SCALAR);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get DELAY_COUNT
		String paramString12(buffer);
		temp1 = paramString12.toFloat();
		sd0[0].DELAY_COUNT = uint32_t(temp1 * SAMPLES_PER_MSEC / ENVELOPE_PERIOD + 0.5);
		Serial.println(sd0[0].DELAY_COUNT);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get ATTACK_COUNT
		String paramString13(buffer);
		temp1 = paramString13.toFloat();
		sd0[0].ATTACK_COUNT = uint32_t(temp1 * SAMPLES_PER_MSEC / ENVELOPE_PERIOD + 0.5);
		Serial.println(sd0[0].ATTACK_COUNT);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get HOLD_COUNT
		String paramString14(buffer);
		temp1 = paramString14.toFloat();
		sd0[0].HOLD_COUNT = uint32_t(temp1 * SAMPLES_PER_MSEC / ENVELOPE_PERIOD + 0.5);
		Serial.println(sd0[0].HOLD_COUNT);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get DECAY_COUNT
		String paramString15(buffer);
		temp1 = paramString15.toFloat();
		sd0[0].DECAY_COUNT = uint32_t(temp1 * SAMPLES_PER_MSEC / ENVELOPE_PERIOD + 0.5);
		Serial.println(sd0[0].DECAY_COUNT);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get RELEASE_COUNT
		String paramString16(buffer);
		temp1 = paramString16.toFloat();
		sd0[0].RELEASE_COUNT = uint32_t(temp1 * SAMPLES_PER_MSEC / ENVELOPE_PERIOD + 0.5);
		Serial.println(sd0[0].RELEASE_COUNT);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get SUSTAIN_MULT
		String paramString17(buffer);
		temp1 = paramString17.toFloat();
		sd0[0].SUSTAIN_MULT = int32_t((1.0 - DECIBEL_SHIFT(temp1)) * UNITY_GAIN);
		Serial.println(sd0[0].SUSTAIN_MULT);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get VIBRATO_DELAY
		String paramString18(buffer);
		temp1 = paramString18.toFloat();
		sd0[0].VIBRATO_DELAY = uint32_t(temp1 * SAMPLES_PER_MSEC / (2 * LFO_PERIOD));
		Serial.println(sd0[0].VIBRATO_DELAY);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get VIBRATO_INCREMENT
		String paramString19(buffer);
		temp1 = paramString19.toFloat();
		sd0[0].VIBRATO_INCREMENT = uint32_t(temp1 * LFO_PERIOD * (UINT32_MAX / AUDIO_SAMPLE_RATE_EXACT));
		Serial.println(sd0[0].VIBRATO_INCREMENT);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get VIBRATO_PITCH_COEFICIENT_INITIAL
		String paramString20(buffer);
		temp1 = paramString20.toFloat();
		sd0[0].VIBRATO_PITCH_COEFFICIENT_INITIAL = (CENTS_SHIFT(temp1) - 1.0) * 4;
		Serial.println(sd0[0].VIBRATO_PITCH_COEFFICIENT_INITIAL);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get VIBRATO_PITCH_COEFICIENT_SECOND
		String paramString21(buffer);
		temp1 = paramString21.toFloat();
		sd0[0].VIBRATO_PITCH_COEFFICIENT_SECOND = (1.0 - CENTS_SHIFT(temp1)) * 4;
		Serial.println(sd0[0].VIBRATO_PITCH_COEFFICIENT_SECOND);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get MODULATION_DELAY
		String paramString22(buffer);
		temp1 = paramString22.toFloat();
		sd0[0].MODULATION_DELAY = uint32_t(temp1 * SAMPLES_PER_MSEC / (2 * LFO_PERIOD)),
			Serial.println(sd0[0].MODULATION_DELAY);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get MODULATION_INCREMENT
		String paramString23(buffer);
		temp1 = paramString23.toFloat();
		sd0[0].MODULATION_INCREMENT = uint32_t(temp1 * LFO_PERIOD * (UINT32_MAX / AUDIO_SAMPLE_RATE_EXACT));
		Serial.println(sd0[0].MODULATION_INCREMENT);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get MODULATION_PITCH_COEFFICIENT_INITIAL
		String paramString24(buffer);
		temp1 = paramString24.toInt();
		sd0[0].MODULATION_PITCH_COEFFICIENT_INITIAL = (CENTS_SHIFT(temp1) - 1.0) * 4;
		Serial.println(sd0[0].MODULATION_PITCH_COEFFICIENT_INITIAL);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get MODULATION_PITCH_COEFFICIENT_SECOND
		String paramString25(buffer);
		temp1 = paramString25.toInt();
		sd0[0].MODULATION_PITCH_COEFFICIENT_SECOND = (1.0 - CENTS_SHIFT(temp1)) * 4;
		Serial.println(sd0[0].MODULATION_PITCH_COEFFICIENT_SECOND);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get MODULATION_AMPLITUDE_INITIAL_GAIN
		String paramString26(buffer);
		temp1 = paramString26.toInt();
		sd0[0].MODULATION_AMPLITUDE_INITIAL_GAIN = (int32_t)(UINT16_MAX * (DECIBEL_SHIFT(temp1) - 1.0)) * 4;
		Serial.println(sd0[0].MODULATION_AMPLITUDE_INITIAL_GAIN);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get MODULATION_AMPLITUDE_SECOND_GAIN
		String paramString27(buffer);
		temp1 = paramString27.toInt();
		sd0[0].MODULATION_AMPLITUDE_SECOND_GAIN = (int32_t)(UINT16_MAX * (1.0 - DECIBEL_SHIFT(temp1))) * 4;
		Serial.println(sd0[0].MODULATION_AMPLITUDE_SECOND_GAIN);
		l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get closing parenthesis
		Serial.println(buffer);

		for (int k = 1; k < numRanges; k++) {
			lcd.print(".");  // progress indicator
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get opening parenthesis (except in last record
			Serial.println(buffer);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get region name 
			Serial.println(buffer);

			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get loop true/false
			String paramString28(buffer);
			sIndex = paramString28.indexOf("true", 0);
			if (sIndex >= 0) {
				sd0[k].LOOP = true;
				Serial.println("true");
			}
			else {
				sd0[k].LOOP = false;
				Serial.println("false");
			}
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get INDEX_BITS
			String paramString29(buffer);
			sd0[k].INDEX_BITS = paramString29.toInt();
			Serial.println(sd0[k].INDEX_BITS);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get cents_offset
			String paramString30(buffer);
			cents_offset = paramString30.toInt();
			Serial.println(cents_offset);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get sample_rate
			String paramString31(buffer);
			sample_rate = paramString31.toFloat();
			Serial.println(sample_rate);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get sample_note
			String paramString32(buffer);
			sample_note = paramString32.toFloat();
			Serial.println(sample_note);
			// calculate PER_HERTZ_PHASE_INCREMENT
			sd0[k].PER_HERTZ_PHASE_INCREMENT = (1 << (32 - sd0[k].INDEX_BITS)) * CENTS_SHIFT(cents_offset) * sample_rate / NOTE(sample_note) / AUDIO_SAMPLE_RATE_EXACT + 0.5;
			Serial.println(sd0[k].PER_HERTZ_PHASE_INCREMENT);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get MAX_PHASE
			String paramString33(buffer);
			temp1 = paramString33.toInt();
			Serial.println(temp1);
			sd0[k].MAX_PHASE = ((uint32_t)temp1 - 1) << (32 - sd0[k].INDEX_BITS);
			Serial.println(sd0[k].MAX_PHASE);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get LOOP_PHASE_END
			String paramString34(buffer);
			temp1 = paramString34.toInt();
			tempT = temp1;
			Serial.println(temp1);
			sd0[k].LOOP_PHASE_END = ((uint32_t)temp1 - 1) << (32 - sd0[k].INDEX_BITS);
			Serial.println(sd0[k].LOOP_PHASE_END);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get LOOP_PHASE_LENGTH
			String paramString35(buffer);
			temp1 = paramString35.toInt();
			Serial.println(temp1);
			sd0[k].LOOP_PHASE_LENGTH = ((((uint32_t)tempT - 1) << (32 - sd0[k].INDEX_BITS)) - (((uint32_t)temp1 - 1) << (32 - sd0[k].INDEX_BITS)));
			Serial.println(sd0[k].LOOP_PHASE_LENGTH);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get INITIAL_ATTENUATION_SCALAR
			String paramString36(buffer);
			temp1 = paramString36.toInt();
			sd0[k].INITIAL_ATTENUATION_SCALAR = (uint16_t)UINT16_MAX * DECIBEL_SHIFT(temp1);
			Serial.println(sd0[k].INITIAL_ATTENUATION_SCALAR);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get DELAY_COUNT
			String paramString37(buffer);
			temp1 = paramString37.toFloat();
			sd0[k].DELAY_COUNT = uint32_t(temp1 * SAMPLES_PER_MSEC / ENVELOPE_PERIOD + 0.5);
			Serial.println(sd0[k].DELAY_COUNT);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get ATTACK_COUNT
			String paramString38(buffer);
			temp1 = paramString38.toFloat();
			sd0[k].ATTACK_COUNT = uint32_t(temp1 * SAMPLES_PER_MSEC / ENVELOPE_PERIOD + 0.5);
			Serial.println(sd0[k].ATTACK_COUNT);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get HOLD_COUNT
			String paramString39(buffer);
			temp1 = paramString39.toFloat();
			sd0[k].HOLD_COUNT = uint32_t(temp1 * SAMPLES_PER_MSEC / ENVELOPE_PERIOD + 0.5);
			Serial.println(sd0[k].HOLD_COUNT);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get DECAY_COUNT
			String paramString40(buffer);
			temp1 = paramString40.toFloat();
			sd0[k].DECAY_COUNT = uint32_t(temp1 * SAMPLES_PER_MSEC / ENVELOPE_PERIOD + 0.5);
			Serial.println(sd0[k].DECAY_COUNT);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get RELEASE_COUNT
			String paramString41(buffer);
			temp1 = paramString41.toFloat();
			sd0[k].RELEASE_COUNT = uint32_t(temp1 * SAMPLES_PER_MSEC / ENVELOPE_PERIOD + 0.5);
			Serial.println(sd0[k].RELEASE_COUNT);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get SUSTAIN_MULT
			String paramString42(buffer);
			temp1 = paramString42.toFloat();
			sd0[k].SUSTAIN_MULT = int32_t((1.0 - DECIBEL_SHIFT(temp1)) * UNITY_GAIN);
			Serial.println(sd0[k].SUSTAIN_MULT);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get VIBRATO_DELAY
			String paramString43(buffer);
			temp1 = paramString43.toFloat();
			sd0[k].VIBRATO_DELAY = uint32_t(temp1 * SAMPLES_PER_MSEC / (2 * LFO_PERIOD));
			Serial.println(sd0[k].VIBRATO_DELAY);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get VIBRATO_INCREMENT
			String paramString44(buffer);
			temp1 = paramString44.toFloat();
			sd0[k].VIBRATO_INCREMENT = uint32_t(temp1 * LFO_PERIOD * (UINT32_MAX / AUDIO_SAMPLE_RATE_EXACT));
			Serial.println(sd0[k].VIBRATO_INCREMENT);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get VIBRATO_PITCH_COEFICIENT_INITIAL
			String paramString45(buffer);
			temp1 = paramString45.toFloat();
			sd0[k].VIBRATO_PITCH_COEFFICIENT_INITIAL = (CENTS_SHIFT(temp1) - 1.0) * 4;
			Serial.println(sd0[k].VIBRATO_PITCH_COEFFICIENT_INITIAL);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get VIBRATO_PITCH_COEFICIENT_SECOND
			String paramString46(buffer);
			temp1 = paramString46.toFloat();
			sd0[k].VIBRATO_PITCH_COEFFICIENT_SECOND = (1.0 - CENTS_SHIFT(temp1)) * 4;
			Serial.println(sd0[k].VIBRATO_PITCH_COEFFICIENT_SECOND);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get MODULATION_DELAY
			String paramString47(buffer);
			temp1 = paramString47.toFloat();
			sd0[k].MODULATION_DELAY = uint32_t(temp1 * SAMPLES_PER_MSEC / (2 * LFO_PERIOD)),
				Serial.println(sd0[k].MODULATION_DELAY);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get MODULATION_INCREMENT
			String paramString48(buffer);
			temp1 = paramString48.toFloat();
			sd0[k].MODULATION_INCREMENT = uint32_t(temp1 * LFO_PERIOD * (UINT32_MAX / AUDIO_SAMPLE_RATE_EXACT));
			Serial.println(sd0[k].MODULATION_INCREMENT);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get MODULATION_PITCH_COEFFICIENT_INITIAL
			String paramString49(buffer);
			temp1 = paramString49.toInt();
			sd0[k].MODULATION_PITCH_COEFFICIENT_INITIAL = (CENTS_SHIFT(temp1) - 1.0) * 4;
			Serial.println(sd0[k].MODULATION_PITCH_COEFFICIENT_INITIAL);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get MODULATION_PITCH_COEFFICIENT_SECOND
			String paramString50(buffer);
			temp1 = paramString50.toInt();
			sd0[k].MODULATION_PITCH_COEFFICIENT_SECOND = (1.0 - CENTS_SHIFT(temp1)) * 4;
			Serial.println(sd0[k].MODULATION_PITCH_COEFFICIENT_SECOND);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get MODULATION_AMPLITUDE_INITIAL_GAIN
			String paramString51(buffer);
			temp1 = paramString51.toInt();
			sd0[k].MODULATION_AMPLITUDE_INITIAL_GAIN = (int32_t)(UINT16_MAX * (DECIBEL_SHIFT(temp1) - 1.0)) * 4;
			Serial.println(sd0[k].MODULATION_AMPLITUDE_INITIAL_GAIN);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get MODULATION_AMPLITUDE_SECOND_GAIN
			String paramString52(buffer);
			temp1 = paramString52.toInt();
			sd0[k].MODULATION_AMPLITUDE_SECOND_GAIN = (int32_t)(UINT16_MAX * (1.0 - DECIBEL_SHIFT(temp1))) * 4;
			Serial.println(sd0[k].MODULATION_AMPLITUDE_SECOND_GAIN);
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get closing parenthesis
			Serial.println(buffer);
		}

		uint32_t accumulator = 0;  // accumulate the wave file lengths of each region, and use to make pointers into SAMPL_BUFFER
		for (int k = 0; k < numRanges; k++) {
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get blank line
			l = myFile.readBytesUntil('\n', buffer, sizeof(buffer));   // get  wavetable sample size
			String paramString78(buffer);    // isolate length of region of waveform samples- then used as pointer to next region
			sIndex = paramString78.indexOf("[", 0);
			sIndex++;
			fIndex = paramString78.indexOf("]", 0);
			String tmpString3 = paramString78.substring(sIndex, fIndex);
			uint16_t temp2 = tmpString3.toInt();
			sd0[k].sample = (int16_t*)&SAMPLE_BUFFER[accumulator];
			accumulator += temp2;
		}
		displayProgramName();
		myFile.close();
	}
	if (err == -1) {
		voiceLoading = false;
		return err;
	}
		voiceLoading = false;
		return err;
}

void checkSDCard() {
	while (!(SD.begin(BUILTIN_SDCARD))) {
		// stop here, but print a message repetitively
		lcd.clear();
		lcd.print("Can't access SD card");
		delay(1000);
		lcd.clear();
		delay(1000);
	}
}


int allocateVoice(byte channel, byte note) {
	for (int i = 1; i < TOTAL_VOICES; i++) {
		if (noteAlloc[i] == 0) {
			noteAlloc[i] = note;
			return i;
			break;
		}
	}
	return -1;
}
void freeVoices() {
	for (int i = 0; i < TOTAL_VOICES; i++)
		if (wavetable[i].isPlaying() == false && noteAlloc[i] == -1) {
			noteAlloc[i] = 0;
		}
}

int findVoice(byte channel, byte note) {
	for (int i = 1; i < TOTAL_VOICES; i++) {
		if (noteAlloc[i] == note) {
			noteAlloc[i] = -1;
			return i;
			break;
		}
		}
	return -1;
	}

void handleNoteOn(byte channel, byte note, byte velocity) {
	digitalWrite(LED, HIGH);
	notes_played++;
	Serial.print("note= ");
	Serial.println(note);
	int wavetable_id = allocateVoice(channel, note);
	Serial.println(wavetable_id);
	wavetable[wavetable_id].playNote(note, velocity);
	int usage = AudioProcessorUsage();
	Serial.print("CPU= ");
	Serial.println(usage);
}

void handleNoteOff(byte channel, byte note, byte velocity) {
	digitalWrite(LED, LOW);
	int wavetable_id = findVoice(channel, note);
	if (wavetable_id != -1)
		wavetable[wavetable_id].stop();
}

void handleProgramChange(byte channel, byte number) {
	if (voiceLoading == true) {
		return;
	}
	programNumber = number;
	if (programNumber == 0) {
		programNumber=1;
	}
	for (int i = 0; i < TOTAL_VOICES;i++) {  // silence all voices
		wavetable[i].stop();
	}
	loadVoice(programNumber);
}
void handleControlChange(byte channel, byte number, byte value) {
	controlNumber = number; 
	controlValue = value;
	currentControlValue = (int) value;
	
	if (controlNumber == 1) {    // Modulation Wheel
		for (int i = 0; i < numRanges; i++) {
			sd0[i].MODULATION_PITCH_COEFFICIENT_INITIAL = (float)controlValue / 512.0;
			sd0[i].MODULATION_PITCH_COEFFICIENT_SECOND = (float)controlValue/ 512.0;
		}
	}
	if (controlNumber == 7) {   // MIDI volume
		float gain;
		// simulate an Audio Taper pot
		if (controlValue < 64) {
			gain =(float) controlValue* (0.1 / 64.0);
		}
		else {
			gain = 0.1 + ((controlValue - 64) * 0.9 / 64);
		}
		mixer3.gain(0, gain);
		mixer3.gain(1,gain );
	}
	 // now process the 7 filter gain controllers
	if (learnMode == false) {  // don't process filter changes while in learn mode
		if (controlNumber == cfg.Control_Translation_Map[0]) adjustFilter1();
		if (controlNumber == cfg.Control_Translation_Map[1]) adjustFilter2();
		if (controlNumber == cfg.Control_Translation_Map[2]) adjustFilter3();
		if (controlNumber == cfg.Control_Translation_Map[3]) adjustFilter4();
		if (controlNumber == cfg.Control_Translation_Map[4]) adjustFilter5();
		if (controlNumber == cfg.Control_Translation_Map[5]) adjustFilter6();
		if (controlNumber == cfg.Control_Translation_Map[6]) adjustFilter7();
	}
}


void handlePitchBendChange(byte channel, int pbValue) {
	Serial.print("pitchbend= ");
	Serial.println(pbValue);

}

void handleAfterTouchChannel(byte channel, byte pressure) {
	for (int i = 0; i < numRanges; i++) {
		sd0[i].MODULATION_PITCH_COEFFICIENT_INITIAL = (float)pressure / 512.0;
		sd0[i].MODULATION_PITCH_COEFFICIENT_SECOND = (float)pressure / 512.0;
	}
}

void initConfigFile() {
			for (int i = 0; i < 16; i++) {
			cfg.Control_Translation_Map[i] = 0;  // zero out the slider to filter adjust map (only 7 of 16 used at present)
		}
		for (int i = 0; i < 128; i++) {
			cfg.filter1_value[i] = 64;  // set all filter gains to mid-point
			cfg.filter2_value[i] = 64;
			cfg.filter3_value[i] = 64;
			cfg.filter4_value[i] = 64;
			cfg.filter5_value[i] = 64;
			cfg.filter6_value[i] = 64;
			cfg.filter7_value[i] = 64;
		}
		SD.remove("config.txt");
		myFile = SD.open("config.txt",FILE_WRITE);
		if (myFile) {
			Serial.println("writing cfg file");
			for (int i = 0; i < 16; i++) {
				myFile.println(cfg.Control_Translation_Map[i]);
			}
			for (int i = 0; i < 128; i++) {
				myFile.println(cfg.filter1_value[i]);  
				myFile.println(cfg.filter2_value[i]);
				myFile.println(cfg.filter3_value[i]);
				myFile.println(cfg.filter4_value[i]);
				myFile.println(cfg.filter5_value[i]);
				myFile.println(cfg.filter6_value[i]);
				myFile.println(cfg.filter7_value[i]);
			}
			cfg.MIDI_channel = 0;
			myFile.println(cfg.MIDI_channel);
			Serial.println("file written");
			myFile.close();
		}
}

void loadConfigFile() {
	myFile = SD.open("config.txt");
	if (myFile) {
		for (int i = 0; i < 16; i++) {
			cfg.Control_Translation_Map[i] = myFile.parseInt();
			Serial.println(cfg.Control_Translation_Map[i]);
		}
		for (int i = 0; i < 128; i++) {
			cfg.filter1_value[i]= myFile.parseInt();
			cfg.filter2_value[i] = myFile.parseInt();
			cfg.filter3_value[i] = myFile.parseInt();
			cfg.filter4_value[i] = myFile.parseInt();
			cfg.filter5_value[i] = myFile.parseInt();
			cfg.filter6_value[i] = myFile.parseInt();
			cfg.filter7_value[i] = myFile.parseInt();
		}
		cfg.MIDI_channel = myFile.parseInt();

		myFile.close();
	}
}

void saveConfigFile() {
	SD.remove("config.txt");
	myFile = SD.open("config.txt", FILE_WRITE);
	if (myFile) {
		Serial.println("writing cfg file");
		for (int i = 0; i < 16; i++) {
			myFile.println(cfg.Control_Translation_Map[i]);
		}
		for (int i = 0; i < 128; i++) {
			myFile.println(cfg.filter1_value[i]);
			myFile.println(cfg.filter2_value[i]);
			myFile.println(cfg.filter3_value[i]);
			myFile.println(cfg.filter4_value[i]);
			myFile.println(cfg.filter5_value[i]);
			myFile.println(cfg.filter6_value[i]);
			myFile.println(cfg.filter7_value[i]);
		}
		myFile.println(cfg.MIDI_channel);
		Serial.println("file written");
		Serial.print("Program #=");
		Serial.println(programNumber);
		myFile.close();
	}
}

void menu() {
	Serial.println("menu");
	while (digitalRead(MENU) == LOW);;;
	delay(100);
	lcd.clear();
	lcd.print("Learn mode- assign");
	lcd.setCursor(0, 1);
	lcd.print("sliders to filters");
	int32_t REValue = 0;
	REnc.write(REValue);
	while (digitalRead(ENTER) == HIGH) {
		MIDI.read();  // continue handling incoming keyboard events
		RotaryEncoderValue = REnc.read();
		if (RotaryEncoderValue != REValue) {
			menuItem = RotaryEncoderValue;
			if (menuItem < 0) {
				menuItem = 0;
				REnc.write(0);
			}
			if (menuItem > MAX_MENU_ITEMS) {
				menuItem--;
				REnc.write(menuItem);
			}
			Serial.println(menuItem);
			delay(50);
			switch (menuItem) {
			case 0: lcd.clear();
				lcd.print("Learn mode- assign");
				lcd.setCursor(0, 1);
				lcd.print("sliders to filters");
				break;
			case 1:lcd.clear();
				lcd.print("MIDI channel- use ");
				lcd.setCursor(0, 1);
				lcd.print("0 for OMNI Mode");
				break;
			}
			REValue = RotaryEncoderValue;
		}
	}
	lcd.clear();
	delay(50);  // debounce ENTER switch
	while (digitalRead(ENTER) == 0);;;
	delay(150);

	switch (menuItem) {
	case 0: lcd.clear();
		learn();
		break;
	case 1: lcd.clear();
		setMIDIchannel();
		break;
	}
	REnc.write(programNumber);  // Restore RE to current program number
	RotaryEncoderValue = programNumber;
}

void learn() {

	byte lastControl=0;
	learnMode = true;
	Serial.print("in learn mode");
		for ( int i = 1; i < 8; i++) {
			byte lastControlValue = 0;
			lcd.clear();
			lcd.print("move slider for");
			lcd.setCursor(0, 1);
			lcd.print("FILTER #");
			lcd.print(i);
			bool controlMoved = false;
			while (controlMoved == false) {
				MIDI.read();
				if (currentControl != lastControl) {
					controlMoved = true;
				}
			}
			controlMoved = false;
			lastControlValue = currentControlValue;
			while (controlMoved == false) {
				MIDI.read();
				if (currentControlValue != lastControlValue) {
					controlMoved = true;
					lcd.print(" got it");
					delay(1000);
				}
			}
			controlMoved = false;
			cfg.Control_Translation_Map[i-1] = currentControl;
			lastControl = currentControl;
			Serial.print("current control= ");
			Serial.println(currentControl);

	 }
		saveConfigFile();  // save new map to SD card
		lcd.clear();
		lcd.print("Filter slider ");
		lcd.setCursor(0, 1);
		lcd.print("map saved");
		delay(1000);
		Serial.println("leaving learn mode");
		learnMode = false;
		displayProgramName();
	
}

void displayProgramName() {
	lcd.clear();
	lcd.print("Prog#");
	lcd.print(programNumber);
	lcd.setCursor(8, 0);
	lcd.print("MIDI CH=");
	if (cfg.MIDI_channel == 0) {
		lcd.print("OMNI");
	}
	else {
		lcd.print(cfg.MIDI_channel);
	}
	lcd.setCursor(0, 1);
	lcd.print(voiceName);
}


void adjustFilter1() {  // filter 1 amplitude
	float val;
	Serial.print("filter1 value = ");
	Serial.println(127-currentControlValue);
	cfg.filter1_value[programNumber-1] = 127 - currentControlValue;
	val = (float)((127.0 - currentControlValue) / 32.0);
	mixer1.gain(0, val) ;  // 1st BANDPASS
	Serial.print("gain= ");
	Serial.println(val);
}

void adjustFilter2() {  // filter 2 amplitude
	float val;
	Serial.print("filter2 value = ");
	Serial.println(127 - currentControlValue);
	cfg.filter2_value[programNumber-1] = 127 - currentControlValue;
	val = (float)((127.0 - currentControlValue) / 32.0);
	mixer1.gain(1, val);  // 2nd BANDPASS
	Serial.print("gain= ");
	Serial.println(val);
}
void adjustFilter3() {  // filter 3 amplitude
	float val;
	Serial.print("filter3 value = ");
	Serial.println(127 - currentControlValue);
	cfg.filter3_value[programNumber-1] = 127 - currentControlValue;
	val = (float)((127.0 - currentControlValue) / 32.0);
	mixer1.gain(2, val);  // 3rd BANDPASS
	Serial.print("gain= ");
	Serial.println(val);
}
void adjustFilter4() {  // filter 4 amplitude
	float val;
	Serial.print("filter4 value = ");
	Serial.println(127 - currentControlValue);
	cfg.filter4_value[programNumber-1] = 127 - currentControlValue;
	val = (float)((127.0 - currentControlValue) / 32.0);
	mixer1.gain(3, val);  // 4th BANDPASS
	Serial.print("gain= ");
	Serial.println(val);
}
void adjustFilter5() {  // filter 5 amplitude
	float val;
	Serial.print("filter5 value = ");
	Serial.println(127 - currentControlValue);
	cfg.filter5_value[programNumber-1] = 127 - currentControlValue;
	val = (float)((127.0 - currentControlValue) / 32.0);
	mixer2.gain(0, val);  // 5th BANDPASS
	Serial.print("gain= ");
	Serial.println(val);
}
void adjustFilter6() {  // filter 6 amplitude
	float val;
	Serial.print("filter6 value = ");
	Serial.println(127 - currentControlValue);
	cfg.filter6_value[programNumber - 1] = 127 - currentControlValue;
	val = (float)((127.0 - currentControlValue) / 32.0);
	mixer2.gain(1, val);  // 6th BANDPASS
	Serial.print("gain= ");
	Serial.println(val);
}

void adjustFilter7() {  // filter 7 amplitude
	float val;
	Serial.print("filter7 value = ");
	Serial.println(127 - currentControlValue);
	cfg.filter7_value[programNumber - 1] = 127 - currentControlValue;
	val = (float)((127.0 - currentControlValue) / 32.0);
	mixer2.gain(2, val);  // 7th BANDPASS
	Serial.print("gain= ");
	Serial.println(val);
}

void setMIDIchannel() {
	byte MIDI_channel = 0;
	int32_t REValue = 0;
	REnc.write(REValue);
	lcd.print("MIDI CHANNEL= 0");
	delay(200);
	while (digitalRead(ENTER) == HIGH) {
		MIDI_channel = REnc.read();
		if (MIDI_channel != REValue) {
			if (MIDI_channel ==255) {
				MIDI_channel = 0;
				REnc.write(0);
			}
			if (MIDI_channel > 16) {
				MIDI_channel=16;
				REnc.write(16);
			}
			lcd.clear();
			lcd.print("MIDI channel= ");
			lcd.print(MIDI_channel);
			REValue = MIDI_channel;

		}
	}
	Serial.print("MIDI_channel= ");
	Serial.println(MIDI_channel);
	MIDI.setInputChannel(MIDI_channel);
	cfg.MIDI_channel = MIDI_channel;
	saveConfigFile();
	displayProgramName();
	REnc.write(programNumber);  // restore the RE to equal current program number
}

void setFilters() {
	filter1.setBandpass(0, 150, 0.7);
	filter1.setBandpass(1, 150, 0.7);
	filter1.setBandpass(2, 150, 0.7);
	filter1.setBandpass(3, 150, 0.7);

	filter2.setBandpass(0, 300, 0.7);
	filter2.setBandpass(1, 300, 0.7);
	filter2.setBandpass(2, 300, 0.7);
	filter2.setBandpass(3, 300, 0.7);

	filter3.setBandpass(0, 600, 1.4);
	filter3.setBandpass(1, 600, 1.4);
	filter3.setBandpass(2, 600, 1.4);
	filter3.setBandpass(3, 600, 1.4);

	filter4.setBandpass(0, 1000, 1.4);
	filter4.setBandpass(1, 1000, 1.4);
	filter4.setBandpass(2, 1000, 1.4);
	filter4.setBandpass(3, 1000, 1.4);

	filter5.setBandpass(0, 2000, 1.4);
	filter5.setBandpass(1, 2000, 1.4);
	filter5.setBandpass(2, 2000, 1.4);
	filter5.setBandpass(3, 2000, 1.4);

	filter6.setBandpass(0, 4000, 1.4);
	filter6.setBandpass(1, 4000, 1.4);
	filter6.setBandpass(2, 4000, 1.4);
	filter6.setBandpass(3, 4000, 1.4);

	filter7.setBandpass(0, 7000, 1.4);
	filter7.setBandpass(1, 7000, 1.4);
	filter7.setBandpass(2, 7000, 1.4);
	filter7.setBandpass(3, 7000, 1.4);
}

I hope this helps.
 
Code:
	filter1.setBandpass(0, 150, 0.7);
	filter1.setBandpass(1, 150, 0.7);
	filter1.setBandpass(2, 150, 0.7);
	filter1.setBandpass(3, 150, 0.7);

	filter2.setBandpass(0, 300, 0.7);
	filter2.setBandpass(1, 300, 0.7);
	filter2.setBandpass(2, 300, 0.7);
	filter2.setBandpass(3, 300, 0.7);

That 8 pole bandpass will be very peaky as all the poles are coincident, leaving gaps between the
centre frequencies. It might be an idea to derive standard Butterworth poles if you want a flat-topped
bandpass response with -3dB points of neighbouring filters lining up properly.
 
Thanks to Mark for the Biquad filter definitions, I will add it to my testing sketch and measure the response.
Regards, Dani
 
I inserted Mark's example in the 'Filter' example under Audio. I defined a Biquad BPF with a center frequency of 1000Hz. The response was -3db at 1390Hz and 720Hz, i.e a BW of 670Hz but not symmetrical around 1000Hz. The filter is defined as:

biquad1.setBandpass(0, 1000, 0.7);
biquad1.setBandpass(1, 1000, 0.7);
biquad1.setBandpass(2, 1000, 0.7);
biquad1.setBandpass(3, 1000, 0.7);
 
>but not symmetrical around 1000Hz.

You should use the geometric mean and with that, it is symmetrical.
 
Thanks Jon.
The geometric mean gives 965Hz, i.e. 35Hz bellow the 1000Hz which seemed to be within the measurement error.
 
> The geometric mean gives 965Hz, i.e. 35Hz bellow the 1000Hz

I get:

sqrt(1390*720) = 1000.4 Hz
 
Hi, sorry to interrupt. I have a related question to this:
I need to implement a similar filter, but for each of the 16 channels of a TDM stream, before mixing them to one stereo signal.
I ask if the Teensy 4.1 is capable of this.
Or is maybe better to leave the filters job to a couple of codecs?
Best regards!
 
Status
Not open for further replies.
Back
Top