Code

Code Overview

Our Code is structured so that all the variables and parameters needed are initialized at the top. Afterwards are the TFT and input related setup functions. Below those are the Filtering functions, followed by the interrupt functions used to calculate and output sound. At the very bottom of the file are the main functions that initialize and run everything above.
A link to a Github repository with the rest of our code files is here.

Main Code
/**
 *  ECE 4760 FA22 Final Project
 *  Tommy Chen (tc575), Rhia Malhotra (rm722), Raphael Fortuna (raf269)
 *  Main .c file for audio synthesizer
 */

// DDS Libraries
#include 
#include 
#include 
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "hardware/sync.h"
#include "hardware/spi.h"

// Protothread Library
#include "pt_cornell_rp2040_v1.h"

// TFT Libraries
#include  //C stdlib
#include "hardware/gpio.h" //The hardware GPIO library
#include "pico/time.h" //The pico time library
#include "hardware/irq.h" //The hardware interrupt library
#include "hardware/pwm.h" //The hardware PWM library
#include "hardware/pio.h" //The hardware PIO library
#include "TFTMaster.h" //The TFT Master library

// Capacitive Touch Libraries
#include "hardware/i2c.h"
#include "mpr121.h"

// Input Libraries
#include "hardware/adc.h"

// Fixed-Point Macros
typedef signed int fix15;
#define multfix15(a,b) ((fix15)((((signed long long)(a))*((signed long long)(b)))>>15))
#define float2fix15(a) ((fix15)((a)*32768.0))
#define fix2float15(a) ((float)(a)/32768.0)
#define absfix15(a)    abs(a)
#define int2fix15(a)   ((fix15)(a << 15))
#define fix2int15(a)   ((int)(a >> 15))
#define char2fix15(a)  (fix15)(((fix15)(a)) << 15)
#define divfix(a,b)    (fix15)( (((signed long long)(a)) << 15) / (b))

fix15 ZeroFix15 = float2fix15(0);
const fix15 fix15One = int2fix15(1);
fix5 fourFix15 = int2fix15(4);


/////////////////////////////
/////// START OF PINS ///////
/////////////////////////////

#define PIN_rotaryA 3 //pin 5
#define PIN_rotaryB 2 // pin 4

#define potentiometer 26 // pin 31

#define PIN_toggleGroup 10 // pin 14
#define PIN_oscillGroup 11 // pin 15
#define PIN_filterGroup 12 // pin 16
#define PIN_LFOGroup 13 // pin 17

// I2C Definitions
#define I2C_PORT i2c1
#define I2C_SDA 14
#define I2C_SCL 15

//SPI Configurations (GPIO port number, NOT pin number on Pi Pico)
#define PIN_MISO 4
#define PIN_CS   5
#define PIN_SCK  6
#define PIN_MOSI 7
#define LDAC     8
#define SPI_PORT spi0

// capacitive touch MPR121 Definitions
#define MPR121_ADDR 0x5A
#define MPR121_I2C_FREQ 100000

// set up the SPI channels
void initalizeSPI(){
    // Initialize SPI channel (channel, baud rate set to 20MHz)
    spi_init(SPI_PORT, 20000000) ;

    // Format (channel, data bits per transfer, polarity, phase, order);
    spi_set_format(SPI_PORT, 16, 0, 0, 0);

    // Map SPI signals to GPIO ports
    gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
    gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
    gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
    gpio_set_function(PIN_CS, GPIO_FUNC_SPI) ;
}

// setup the I2C
void setupI2C(){
    i2c_init(I2C_PORT, MPR121_I2C_FREQ);
    gpio_set_function(I2C_SDA, GPIO_FUNC_I2C);
    gpio_set_function(I2C_SCL, GPIO_FUNC_I2C);
    gpio_pull_up(I2C_SDA);
    gpio_pull_up(I2C_SCL);
}

///////////////////////////
/////// END OF PINS ///////
///////////////////////////

#define buttonArraySize 4 // IF YOU CHANGE THE NUMBER OF BUTTONS CHANGE THIS!!

// for easily acessing pins for each core
int buttons[buttonArraySize] = {
    PIN_toggleGroup, 
    PIN_oscillGroup, 
    PIN_filterGroup, 
    PIN_LFOGroup
    }; 

// indexes for button pins above:
#define toggleGroup 0
#define oscillGroup 1 
#define filterGroup 2
#define LFOGroup 3

//////////////////////////////////////////////
/////// START OF GPIO AND BUTTON SETUP ///////
//////////////////////////////////////////////

// get the button value - true is pressed, false is not pressed
bool getButtonStatus(int buttonValue){
    if (gpio_get(buttonValue)){
        //printf("1");
    }
    return !gpio_get(buttonValue);
}

// set up a button
void setUpButton(int buttonValue){
    gpio_init(buttonValue);
    gpio_set_dir(buttonValue, GPIO_IN);
    gpio_pull_up(buttonValue);
}

// set up all the gpio pins hardware
void setUpHardwareGPIO(){
    for (int i = 0; i < buttonArraySize; i++){
        setUpButton(buttons[i]);
    }
    //printf("Hardware setup");
}

////////////////////////////////////////////
/////// END OF GPIO AND BUTTON SETUP ///////
////////////////////////////////////////////

///////////////////////////////////////
/////// START OF ROTARY ENCODER ///////
///////////////////////////////////////

volatile int rotaryCounterData = 1; // updated by interrupt that is getting information from rotary encoder
volatile int tinyCounter = 0; // updated by interrupt that is getting information from rotary encoder

#define tinyCountGranularity 3 // how many clicks is 1 change
#define rotaryCounterWrap 50 // wrap around for rotary counter

// the general function each interrupt calls here
// has a wrap around to avoid unnecessarily high values
void updateRotaryCounter(){

    // if the B pin is high or low
    bool bStatus = gpio_get(PIN_rotaryB);
    
    if (bStatus){
        // left wise - counterclockwise
        tinyCounter += 1;

        // counterclockwise direciton
        if (tinyCounter > tinyCountGranularity){
            tinyCounter = 0; // reset tiny counter
            rotaryCounterData += 1;
            if (rotaryCounterData > rotaryCounterWrap){
                rotaryCounterData = 0; // reset rotary counter
            }
        }
    }

    else{
        // right wise - clockwise
        tinyCounter -= 1;

        // clockwise direciton
        if (tinyCounter < 0){
            tinyCounter = tinyCountGranularity; // reset tiny counter
            rotaryCounterData -= 1;
            if (rotaryCounterData < 0){
                rotaryCounterData = rotaryCounterWrap; // reset rotary counter
            }
        }
    }
}

// setup the rotary pin
void setUpRotaryPin(int pinValue){
    gpio_init(pinValue);
    gpio_set_dir(pinValue, GPIO_IN);
}

// set up a rotary encoder
void setUpRotaryEncoder(){
    // need falling edge of A - 4.1.10.4.43

    // first set up the pins for A, B
    setUpRotaryPin(PIN_rotaryB);
    
    // now set up the GPIO interrupt
    gpio_set_irq_enabled_with_callback(PIN_rotaryA, 
    GPIO_IRQ_EDGE_FALL, true, &updateRotaryCounter);
}

/////////////////////////////////////
/////// END OF ROTARY ENCODER ///////
/////////////////////////////////////

// get the current group that is being modified by the controller, -1 if no change
int getCurrentGroup(){
    if (getButtonStatus(buttons[oscillGroup])){
        //printf("o");
        return oscillGroup;
    }

    else if (getButtonStatus(buttons[filterGroup])){
        //printf("f");
        return filterGroup;
    }
    
    else if (getButtonStatus(buttons[LFOGroup])){
        //printf("l");
    return LFOGroup;
    }

    return -1; // no change
}

// check if toggle button was pressed - true yes, false no
bool getToggleButton(){
    
    if (getButtonStatus(buttons[toggleGroup])){
        //printf("button pressed");
        return true;
    }

    return false; // no change
}

//////////////////////////////////////
/////// START OF POTENTIOMETER ///////
//////////////////////////////////////

float oldPotentiometerValue = 0;
float newPotentiometerValue = 0;

// value change between thye two potentiometer values
float potentiometerDifference = .05;

// returns the value of the potentiometer from 0 to 1 range
fix15 getPotentimeterValue(){
    
    // complete with input
    // Select analog mux input (0...3 are GPIO 26, 27, 28, 29; 4 is temp sensor)
    // adc_select_input(0) ; // so potentimeter is 26
    // adc_select_input(1) ; // so potentimeter is 27

    // this is 1/2^12 since doing it non-decimal way ended up with 0
    float result = adc_read()*0.000244140625;

    // update with new potentiometer value
    newPotentiometerValue = result;

    // needs to return fix15
    return float2fix15(result);
}

// initalize the ADC
void initalizeADC(){

    // Init GPIO for analogue use: hi-Z, no pulls, disable digital input buffer.
    adc_gpio_init(potentiometer);

    // Initialize the ADC harware
    // (resets it, enables the clock, spins until the hardware is ready)
    adc_init() ;

    // select 0, pin 26
    adc_select_input(0);
}

//////////////////////////////////////
/////// END OF POTENTIOMETER /////////
//////////////////////////////////////

// to make it easier to do colors
#define WHITE ILI9340_WHITE
#define BLACK ILI9340_BLACK

// to make it easier to do width, height
#define TFTWidth ILI9340_TFTWIDTH
#define TFTHeight ILI9340_TFTHEIGHT

int lineYShift = 20;
int defaultX = 20;
int textToNumberValueShift = TFTWidth/2; // shift to where numbers are
#define titleShift 30

// needs the extra for the larger oscillator label
int defaultY = titleShift+10;

///////////////////////////////////////////
/////// START OF AUDIO PARAMETERS /////////
///////////////////////////////////////////

// scale a input to a variable's range
fix15 scaleToVariable(fix15 range, fix15 value){
    return multfix15(value, range);
}

// the number of variables for the oscillator
#define oscillatorVarSize 7

// labels for the oscillator variables
char oscillatorVariables[oscillatorVarSize][15] = {
    "On/Off: ",
    "Freq: ",
    "Amp: ",
    "Shape",
    "Attack: ",
    "Decay: ",
    "Release: "
    };

#define OnOff 0
#define Frequency 1
#define Amplitude 2
#define Shape 3
#define Attack 4
#define Decay 5
#define Release 6 

// different waves corresponding to 1, 2, 3, 4
#define squareWave int2fix15(1)
#define sinWave int2fix15(2)
#define triangleWave int2fix15(3)
#define sawtoothWave int2fix15(4)

// boundaries for shapes
fix15 ShapeSelectionBoundaries[3] = {float2fix15(0.25), float2fix15(0.5), float2fix15(0.75)};

// is just a range value, assumes bottom range is 0
fix15 oscillatorValueRanges[oscillatorVarSize] = {
    0, // on off, unused
    float2fix15(1000), // frequency
    float2fix15(1), // amplitude
    0, // shape, unused
    float2fix15(200), //attack
    float2fix15(200), //decay
    float2fix15(300) // release
    };

// values that store the data for the oscillator
fix15 oscillatorValues[oscillatorVarSize] = {int2fix15(1), 0, float2fix15(.5), int2fix15(1), float2fix15(50.0), float2fix15(50.0), float2fix15(100.0)};

// getter for oscillator values
fix15 getOscill(int varName){
    return oscillatorValues[varName];
}

// setter for oscillator values
void setOscill(int varName, fix15 value){

    // no scaling for on/off
    if (varName == OnOff){
        oscillatorValues[varName] = value;
    }

    else if (varName == Shape){

        if (value < ShapeSelectionBoundaries[0]){
            oscillatorValues[varName] = squareWave;
        }
        else if (ShapeSelectionBoundaries[2] < value){
            oscillatorValues[varName] = sawtoothWave;
        }
        else if (value < ShapeSelectionBoundaries[1]){
            oscillatorValues[varName] = sinWave;
        }
        else{
            oscillatorValues[varName] = triangleWave;
        }
    }

    else{
        // scale the input
        value = scaleToVariable(oscillatorValueRanges[varName], value);

        oscillatorValues[varName] = value;
    }
}
    

/////////////////////////////////////////////////////

// how many filter variables there are
#define filterVarSize 4

char filterVariables[filterVarSize][15] = {
    "On/Off: ",
    "Cutoff 1",
    "Cutoff 2",
    "Type: "
    };

// on off already defined
#define cutoff1 1
#define cutoff2 2
#define Type 3

fix15 filterValues[filterVarSize] = {0, float2fix15(100), float2fix15(500), int2fix15(1)};

// getter for filter values
fix15 getFilter(int varName){
    return filterValues[varName];
}

// is just a range value, assumes bottom range is 0
fix15 filterValueRanges[oscillatorVarSize] = {
    0, // on off, unused
    float2fix15(2000), // cutoff 1
    float2fix15(2000), // cutoff 2
    0 // type, unused
    };

// different filtering types corresponding to 1, 2, 3
#define lowPass int2fix15(1)
#define highPass int2fix15(2)
#define bandPass int2fix15(3) 

// boundaries for filter types
fix15 filterPassSelectionBoundaries[2] = {float2fix15(0.333), float2fix15(0.666)};

// setter for filter values
void setFilter(int varName, fix15 value){
    if (varName == Type){

        // three choices

        if (value < filterPassSelectionBoundaries[0]){
            filterValues[varName] = lowPass;
        }
        else if (filterPassSelectionBoundaries[1] < value){
            filterValues[varName] = bandPass;
        }
        else{
            filterValues[varName] = highPass;
        }
    }
    // no scaling for on/off
    else if (varName == OnOff){
        filterValues[varName] = value;
    }
    else{
        // scale cutoff
        value = scaleToVariable(filterValueRanges[varName], value);
        filterValues[varName] = value;
    }
}

/////////////////////////////////////////////////////

#define LFOVarSize 3

char LFOVariables[LFOVarSize][15] = {
    "On/Off",
    "Freq: ",
    "Shape: "
    };

// on off, freq, amp, shape already done
#define LFOShape 2

// ranges for LFO values
fix15 LFOValueRanges[LFOVarSize] = {
    0, // on off, unused
    float2fix15(1000), // frequency 
    0 // shapes, unused
    };

fix15 LFOValues[LFOVarSize] = {0, float2fix15(15), int2fix15(1)};

// getter for LFO values
fix15 getLFO(int varName){
    return LFOValues[varName];
}

// setter for LFO values
void setLFO(int varName, fix15 value){

    // four options
    
    if (varName == LFOShape){

        if (value < ShapeSelectionBoundaries[0]){
            LFOValues[varName] = squareWave;
        }
        else if (ShapeSelectionBoundaries[2] < value){
            LFOValues[varName] = sawtoothWave;
        }
        else if (value < ShapeSelectionBoundaries[1]){
            LFOValues[varName] = sinWave;
        }
        else{
            LFOValues[varName] = triangleWave;
        }
    }

    else{
        // no scaling for on/off
        if (varName == OnOff){
            LFOValues[varName] = value;
        }
        
        else{
            // scale the input
            value = scaleToVariable(LFOValueRanges[varName], value);

            LFOValues[varName] = value;
        }
    }
}

// simple get - look at all settings instead of one at a time
fix15 getSettingGroupValue(int settingGroupSelected, int variableSelected){
    if (settingGroupSelected == oscillGroup){
        return getOscill(variableSelected);
    }
    else if (settingGroupSelected == filterGroup){
        return getFilter(variableSelected);
    }
    else{
        return getLFO(variableSelected);
    }
}


// simple set - look at all settings instead of one at a time
void setSettingGroupValue(int settingGroupSelected, int variableSelected, fix15 value){
    if (settingGroupSelected == oscillGroup){
        setOscill(variableSelected, value);
    }
    else if (settingGroupSelected == filterGroup){
        setFilter(variableSelected, value);
    }
    else{
        setLFO(variableSelected, value);
    }
}

/////////////////////////////////////////////////////

// total number of variables
int numberOfVariables = oscillatorVarSize + filterVarSize + LFOVarSize;

// keep track of what setting group is being modified - only changed by hardware input
int currentSettingGroup = 1;

// keep track of previous setting group - only changed by vga thread
int oldSettingGroup = 1;

// keep track of what variable is being changed by the rotary encoder
int currentVariableSelection = 1;

// the previously selected bariable
int oldCurrentSelection = 0;

// what the size of the current setting group is for the rotary encoder
int currentSettingGroupSize = oscillatorVarSize-1;

// text arrays
static char oscillatorVariablesVGA[oscillatorVarSize][40];
static char filterVariablesVGA[filterVarSize][40];
static char LFOVariablesVGA[LFOVarSize][40];

// Filter type
volatile int filterType = 3; // Low-Pass = 0, High-Pass = 1, Band-Pass = 2

// Default single cutoff
volatile float cutoffFrequency1 = 500; // current cutoff frequency 1
volatile int bin1; // cutoff bin 1 used in FFT

// Used for Band-Pass filter
volatile float cutoffFrequency2 = 1000; // current cutoff frequency 2
volatile int bin2; // cutoff bin 2 used in FFT

/////////////////////////////////////////
/////// END OF AUDIO PARAMETERS /////////
/////////////////////////////////////////


///////////////////////////////////////////
/////// START OF CAPACITIVE TOUCH /////////
///////////////////////////////////////////

// Touch and release thresholds.
#define MPR121_TOUCH_THRESHOLD 20
#define MPR121_RELEASE_THRESHOLD 30

// Touch Sensor Variables
struct mpr121_sensor mpr121;
const uint8_t electrodes[12] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
bool touched[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
bool touched1[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint16_t baseline[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint16_t filtered[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int noiseCounter[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int touchedNote = -1;

// Note Frequency Table (C4 -> B4)
float noteTable[12] = {261.6256, 277.1826, 293.6648, 
                        311.1270, 329.6276, 349.2282,
                        369.9944, 391.9954, 415.3047,
                        440.0000, 466.1638, 493.8833};

// check if the capactivie touch is working
void check_touched(uint8_t electrode){
    mpr121_is_touched(electrode, &touched[electrode], &mpr121);
    mpr121_baseline_value(electrode, &baseline[electrode], &mpr121);
    mpr121_filtered_data(electrode, &filtered[electrode], &mpr121);

    if (touched[electrode] == 1){
        noiseCounter[electrode] = 1;
    }
    else if (noiseCounter[electrode] > 5){
        noiseCounter[electrode] = 0;
    }
    else if (noiseCounter[electrode] >= 1){
        noiseCounter[electrode] += 1;
    }
}

// initalize the touch sensor
void initalizeTouchSensor(){

    // Touch LED Setup
    const uint LED_PIN = PICO_DEFAULT_LED_PIN;
    gpio_init(LED_PIN);
    gpio_set_dir(LED_PIN, GPIO_OUT);

    // setup breakout board for touch
    mpr121_init(I2C_PORT, MPR121_ADDR, &mpr121);
    
    mpr121_set_thresholds(MPR121_TOUCH_THRESHOLD,
                          MPR121_RELEASE_THRESHOLD, &mpr121);

    // Enable all touch sensors
    mpr121_enable_electrodes(12, &mpr121);

    int z = 0;

    // loop used to make sure touch sensor is initalized properly
    while(z < 200) {

        for (int i = 0; i < 12; i++) { // separate each sensor into own thread? or just only run one sound at a time
            check_touched(electrodes[i]);
        }
        z++;
    }

    printf("done with capactive touch setup\n");
}

/////////////////////////////////////////
/////// END OF CAPACITIVE TOUCH /////////
/////////////////////////////////////////


//////////////////////////////
/////// START OF DDS /////////
//////////////////////////////

// Direct Digital Synthesis (DDS)
#define two32 4294967296.0  // 2^32 (constant)
#define Fs    20000            // Sample Rate was 40000

// Phase accumulator and phase increment. Output frequency set via noteFrequency
volatile unsigned int phase_accum_main_0;                  
volatile unsigned int phase_incr_main_0 = 0;

////////////////////////////
/////// END OF DDS /////////
////////////////////////////


/////////////////////////////////////
/////// START OF WAVETABLES /////////
/////////////////////////////////////

// Shape Wavetables - populated in main()
#define lookup_table_size 256
#define amplitude_limit 2047.0

// different wave tables for each type of waveform
float amp_sin = amplitude_limit;
fix15 sin_table[lookup_table_size];
float amp_sqr = amplitude_limit;
fix15 square_table[lookup_table_size];
float amp_tri = amplitude_limit;
fix15 triangle_table[lookup_table_size];
float amp_saw = amplitude_limit;
fix15 sawtooth_table[lookup_table_size];

// Wavetable used for manipulations
fix15 currentWave[lookup_table_size]; 

int LFO_Shape = 1;

fix15 LFO_sin_freq_table[lookup_table_size]; // LFO applied to frequencies
fix15 LFO_square_freq_table[lookup_table_size]; // LFO applied to frequencies
fix15 LFO_triangle_freq_table[lookup_table_size]; // LFO applied to frequencies
fix15 LFO_sawtooth_freq_table[lookup_table_size]; // LFO applied to frequencies

float LFO_sin = amplitude_limit; // amplitude of the LFOs
float LFO_sq = 0; // amplitude of the LFOs
float LFO_tri = 0; // amplitude of the LFOs
float LFO_saw = 0; // amplitude of the LFOs
float LFO_f = 15; // frequency of the LFOs

// Build the sine, square, triangle, and sawtooth lookup tables 
// scaled to produce values between 0 and 4096 (for 12-bit DAC) 
void buildWaveTables(){ 
    float mag_tri = -amp_tri;   
    float inc_tri = 2.0*amp_tri/(lookup_table_size/2);
    float LFO_inc_tri = 1.0/(lookup_table_size/2);
    float mag_saw = 0;  
    float inc_saw = amp_saw/(lookup_table_size/2 - 5);
    float dec_saw = 2.0*amp_saw/10; 
    float LFO_inc_saw = 1.0/(lookup_table_size - 10);
    float LFO_dec_saw = 1.0/10;

    // initalize regular and LFO wavetables
    for (int ii = 0; ii < lookup_table_size; ii++) {

        sin_table[ii] = float2fix15(amp_sin*sin((float)ii*6.283/(float)lookup_table_size));
        LFO_sin_freq_table[ii] = float2fix15((sin((float)ii*6.283/(float)lookup_table_size) + 1.0));



        square_table[ii] = float2fix15(amp_sqr);
        LFO_square_freq_table[ii] = float2fix15(LFO_sq);
        if((ii + 1) % (lookup_table_size/2) == 0) { 
            amp_sqr *= -1;
            LFO_sq = 1.0;
        }


        LFO_triangle_freq_table[ii] = float2fix15(LFO_tri);
        triangle_table[ii] = float2fix15(mag_tri);  
        if(mag_tri >= amp_tri) {    
            inc_tri *= -1;  
            mag_tri = amp_tri;

            LFO_inc_tri *= -1;
            LFO_tri = 1;
        }   
        else if (mag_tri <= -amp_tri) { 
            inc_tri *= -1;  
            mag_tri = -amp_tri;

            LFO_inc_tri *= -1;
            LFO_tri = 0;
        }   
        mag_tri += inc_tri;
        LFO_tri += LFO_inc_tri;

        if (LFO_tri > 1.0){
            LFO_tri = 1.0;
        }
        else if (LFO_tri < 0.0){
            LFO_tri = 0.0;
        }

        sawtooth_table[ii] = float2fix15(mag_saw);  
        if (ii < lookup_table_size/2 - 5){  
            mag_saw += inc_saw;
        }   
        else if (ii < lookup_table_size/2 + 5){ 
            mag_saw -= dec_saw;
        }   
        else {
            mag_saw += inc_saw;
        }

        LFO_sawtooth_freq_table[ii] = float2fix15(LFO_saw);
        if (LFO_saw > 1.0){
            LFO_saw = 1.0;
        }
        else if (LFO_saw < 0.0){
            LFO_saw = 0.0;
        }
        if (ii < lookup_table_size - 10){
            LFO_saw += LFO_inc_saw;
        }
        else {  
            LFO_saw -= LFO_dec_saw;
        }
    }   
}

// populate the current wave table
void populateWave(fix15 waveTable[lookup_table_size]){
    for (int i = 0; i < NUM_SAMPLES; i++) {
    currentWave[i] = waveTable[i];
    fr[i] = currentWave[i];
    fi[i] = 0;
    }
}

///////////////////////////////////
/////// END OF WAVETABLES /////////
///////////////////////////////////


//////////////////////////////
/////// START OF LFO /////////
//////////////////////////////

// LFO frequency setting
volatile unsigned int phase_accum_LFO = 0;
volatile unsigned int phase_incr_LFO = 0;

////////////////////////////
/////// END OF LFO /////////
////////////////////////////



//////////////////////////////
/////// START OF TFT /////////
//////////////////////////////

// add the text labels to the TFT
void setupTFTText(int setting){

    int baseX = defaultX;

    // base value
    int baseY = defaultY;

    // first wipe the correct side
    tft_fillRect(0, 0, TFTWidth, TFTHeight, BLACK);
    
    tft_setTextColor(WHITE) ;
    
    tft_setTextSize(3) ;

    // shift down just for name of core
    tft_setCursor(baseX, baseY-titleShift) ;

    tft_writeString("Synthesizer");
    
    tft_setTextSize(2) ;   

    if (setting == oscillGroup){
        // oscillator variables
        tft_setCursor(baseX, baseY) ;
        tft_writeString("Oscilliscope");
        baseY += lineYShift;

        for (int i = 0; i < oscillatorVarSize; i++){
            tft_setCursor(baseX, baseY) ;
            tft_writeString(oscillatorVariables[i]) ;
            baseY += lineYShift; // go to next line
        }
    }

    if (setting == filterGroup){
        // filter variables
        tft_setCursor(baseX, baseY) ;
        tft_writeString("Filter");
        baseY += lineYShift;

        for (int i = 0; i < filterVarSize; i++){
            tft_setCursor(baseX, baseY) ;
            tft_writeString(filterVariables[i]) ;
            baseY += lineYShift; // go to next line
        }
    }

    if (setting == LFOGroup){
        // LFO variables
        tft_setCursor(baseX, baseY) ;
        tft_writeString("LFO");
        baseY += lineYShift;

        for (int i = 0; i < LFOVarSize; i++){
            tft_setCursor(baseX, baseY) ;
            tft_writeString(LFOVariables[i]) ;
            baseY += lineYShift; // go to next line
        }
    }
}
// if the group setting has changed
bool groupSettingUpdated = false;

bool numbersUpdated = true;

// old setting selector
int oldSettingSelector = 0;

// update which setting is currently being selected
void updateSettingSelector(){

    if (oldSettingSelector != currentVariableSelection || groupSettingUpdated){
        oldSettingSelector = currentVariableSelection;

        //printf("selector1\n");
        // base value
        int baseX = defaultX;
        
        int baseY = defaultY;

        // first wipe the correct side
        tft_fillRect(0, 0, baseX, TFTHeight, BLACK);
        
        tft_setTextColor(WHITE) ;
        
        // now draw a little circle
        int xCircle = baseX - defaultX/2;

        // + 1 i for the name of the setting is for the title
        //+.5 for being in the center of the word
        int yCircle = baseY + (currentVariableSelection+1.5)*lineYShift;

        // now draw the selector circle
        // can also use tft_fillCircle if it looks better
        tft_drawCircle(xCircle, yCircle, defaultX/4, WHITE);
    }
}

// check if the setting group has changed
int checkSettingGroup(){
    //printf("setting1\n");
    if (oldSettingGroup != currentSettingGroup){
        oldSettingGroup = currentSettingGroup;
        //printf("setting2\n");

        // wipe text and numbers and redraw the text
        setupTFTText(oldSettingGroup);
    }
}

// update the numbers for the settings
void updateNumbers(){
    //printf("number1\n");

    int settingGroupSelected = oldSettingGroup;

    // move to other core location if needed and go to the number locations
    int baseX = defaultX + textToNumberValueShift;

    // base value
    int baseY = defaultY+lineYShift;

    tft_setTextColor(WHITE) ;
    tft_setTextSize(2) ;
    tft_setCursor(baseX, baseY) ;

    int y_range = numberOfVariables*(1+lineYShift);

    // erase old numbers
    tft_fillRect(baseX, baseY, TFTWidth-defaultX + textToNumberValueShift, y_range, BLACK);

    // draw new numbers

    if (settingGroupSelected == oscillGroup){
        // oscillator variables
        //printf("number2\n");
        for (int i = 0; i < oscillatorVarSize; i++){
            sprintf(oscillatorVariablesVGA[i], "%f", fix2float15(getOscill(i))) ;
            tft_setCursor(baseX, baseY) ;
            tft_writeString(oscillatorVariablesVGA[i]) ;
            baseY += lineYShift; // go to next line
        }
    }

    else if(settingGroupSelected == filterGroup){
        // filter variables
        for (int i = 0; i < filterVarSize; i++){
            //printf("number3\n");
            sprintf(filterVariablesVGA[i], "%f", fix2float15(getFilter(i))) ;
            tft_setCursor(baseX, baseY) ;
            tft_writeString(filterVariablesVGA[i]) ;
            baseY += lineYShift; // go to next line
        }
    }

    else if(settingGroupSelected == LFOGroup){
        // LFO variables
        for (int i = 0; i < LFOVarSize; i++){
            //printf("number4\n");
            sprintf(LFOVariablesVGA[i], "%f", fix2float15(getLFO(i))) ;
            tft_setCursor(baseX, baseY) ;
            tft_writeString(LFOVariablesVGA[i]) ;
            baseY += lineYShift; // go to next line
        }
    }
}

// initalize the TFT pannel
void initalizeTFT(){
    tft_init_hw(); //Initialize the hardware for the TFT
    tft_begin(); //Initialize the TFT
    tft_setRotation(3); //Set TFT rotation
    tft_fillScreen(BLACK); //Fill the entire screen with black colour
}

// update the TFT screen in the interrupt
bool updateTFT(struct repeating_timer *t){

    // update setting group if needed
    checkSettingGroup();

    // update the selector
    updateSettingSelector();

    if (numbersUpdated){
        // update the numbers
        updateNumbers();

        numbersUpdated = false;
    }
    return true;
}

////////////////////////////
/////// END OF TFT /////////
////////////////////////////


///////////////////////////////////
/////// START OF HARDWARE /////////
///////////////////////////////////

// update the oscilliator values using the hardware
void getNewHardwareValues(){

    // second if the button was pressed to turn setting group on/off
    if (getToggleButton()){

        // need to toggle the current setting group on/off
        fix15 currentValue = getSettingGroupValue(currentSettingGroup, OnOff);
        if (currentValue == 0){
            setSettingGroupValue(currentSettingGroup, OnOff, fix15One);
        }
        else{
            setSettingGroupValue(currentSettingGroup, OnOff, 0);
        }

        numbersUpdated = true;
    }

    // third check which state the buttons are in
    int buttonState = getCurrentGroup();

    if (buttonState != -1){
        // button was pressed
        if (buttonState != currentSettingGroup){
            // setting group has changed

            groupSettingUpdated = true; // there is a new group
            currentSettingGroup = buttonState;
            numbersUpdated = true; // number will change
            currentVariableSelection = 0;
            rotaryCounterData = 0; // this has the potential to break things WARNING!!!!

            // update group size, -1 since not including the on/off
            if (buttonState == oscillGroup){
                currentSettingGroupSize = oscillatorVarSize - 1;
            }
            else if (buttonState == filterGroup){
                currentSettingGroupSize = filterVarSize - 1;
            }
            else{
                currentSettingGroupSize = LFOVarSize - 1;
            }
        }
    }

    // now get the current setting selected from the rotary encoder, +1 to avoid the on/off setting
    currentVariableSelection = (rotaryCounterData%currentSettingGroupSize)+1;

    // see if can change values
    if (currentVariableSelection != oldCurrentSelection || groupSettingUpdated){
        getPotentimeterValue();

        bool significantChange = false;
        
        if ((oldPotentiometerValue - newPotentiometerValue) < 0){
            // negative, cannot use abs since is for ints
            // -.1 vs -.05 
            if ((oldPotentiometerValue - newPotentiometerValue) < -potentiometerDifference){
                // value has significantly changed
                significantChange = true;
            }
        }
        // .1 vs .05
        else if (oldPotentiometerValue - newPotentiometerValue > potentiometerDifference){
                // value has significantly changed
                significantChange = true;
            }

        if (significantChange){

            // numbers have changed
            numbersUpdated = true;

            // change the actual setting group value
            setSettingGroupValue(currentSettingGroup, currentVariableSelection, getPotentimeterValue());

            // update potentiometer value
            oldPotentiometerValue = newPotentiometerValue;

            // update group setting value
            groupSettingUpdated = false;

            // update variable selection value
            oldCurrentSelection = currentVariableSelection;
        }
    }
    else{
        // currently changing a value
        numbersUpdated = true;

        // change the actual setting group value
        setSettingGroupValue(currentSettingGroup, currentVariableSelection, getPotentimeterValue());

        oldPotentiometerValue = newPotentiometerValue;
    }
}

// update the hardware values in a interrupt
bool updateHardware(struct repeating_timer *t){

    getNewHardwareValues();
    return true;
}

/////////////////////////////////
/////// END OF HARDWARE /////////
/////////////////////////////////


///////////////////////////////
/////// START OF ADSR /////////
///////////////////////////////

// ADSR Variables
volatile fix15 max_amplitude = int2fix15(1);    // maximum amplitude
volatile fix15 attack_inc;                       // rate at which sound ramps up
volatile fix15 decay_inc;                        // rate at which sound ramps down
fix15 current_amplitude_0 = int2fix15(0);        // current amplitude (modified in ISR)

// ADSR Timing (units of interrupts)
volatile int attack = 50;
volatile int decay = 10;
volatile int release = 1;
volatile int ADSRcount = 0;
volatile int releaseFlag = 0;

// ADSR function
void ADSR(int attack, int decay, int release) {

    fix15 attack_inc = float2fix15(1.25 / attack);
    fix15 decay_dec = float2fix15(0.25 / decay);
    fix15 release_dec = float2fix15(1.0 / release);

    // iterate through twelve notes to see which one was touched
    for (int j = 0; j < 12; j++){

        mpr121_is_touched(j, &touched[j], &mpr121);

        if (touched[j]){
            phase_incr_main_0 = (noteTable[j]*two32)/Fs;

            // write tft
            if (oscillatorValues[Frequency] != float2fix15(noteTable[j])){
                oscillatorValues[Frequency] = float2fix15(noteTable[j]);
                updateNumbers(); // this makes it a tad faster when updating the value in the tft
            }
            touchedNote = j;
            break;
        }
    }

    // modulate the attack and decay based on touch
    if (touched[touchedNote]){
        if (ADSRcount < attack){
            current_amplitude_0 += attack_inc;
            ADSRcount += 1;
        }
        else if (ADSRcount < attack + decay){
            current_amplitude_0 -= decay_dec;
            ADSRcount += 1;
        }
        noiseCounter[touchedNote] = 1;
    }  

    else if (noiseCounter[touchedNote] > 8){

        current_amplitude_0 -= release_dec;

        // avoid negatives
        if (current_amplitude_0 < ZeroFix15){
            current_amplitude_0 = ZeroFix15;
        }
        noiseCounter[touchedNote] = 0;
    }

    else if (noiseCounter[touchedNote] >= 1){
        if (ADSRcount < attack){
            current_amplitude_0 += attack_inc;
            ADSRcount += 1;
        }

        else if (ADSRcount < attack + decay){
            current_amplitude_0 -= decay_dec;
            ADSRcount += 1;
        }
        
        noiseCounter[touchedNote] += 1;
    }

    else if(noiseCounter[touchedNote] == 0){
        // printf("%d\n", fix2float15(release_dec));
        current_amplitude_0 -= release_dec;//float2fix15(0.01);

        if (current_amplitude_0 < ZeroFix15){
            current_amplitude_0 = ZeroFix15;
        }
        
        ADSRcount = 0;
    }
}

/////////////////////////////
/////// END OF ADSR /////////
/////////////////////////////


// DAC Output Values
int DAC_output_0;

// SPI Data
uint16_t DAC_data_0; // output value

// DAC Parameters
// B-channel, 1x, active
#define DAC_config_chan_B 0b1011000000000000

//////////////////////////////
/////// START OF FFT /////////
//////////////////////////////

// Fast Fourier Transform (FFT)
#define NUM_SAMPLES 256     // Number of samples per FFT
#define NUM_SAMPLES_M_1 255 // Number of samples per FFT, minus 1
#define SHIFT_AMOUNT 8      // Length of short (16 bits) minus log2 number of samples (8)
#define LOG2_NUM_SAMPLES 8  // Log2 number of samples
#define ADCCLK 48000000.0   // ADC clock rate (unmutable)

// Alpha Max + Beta Min Variables
fix15 zero_point_4 = float2fix15(0.4); // 0.4 in fixed point

// FFT Calculation Arrays (real and imaginary)
fix15 fr[NUM_SAMPLES];
fix15 fi[NUM_SAMPLES];
fix15 fin[NUM_SAMPLES];

// FFT Sine Table
fix15 fft_sinewave[NUM_SAMPLES];

// In-Place FFT Function
// Adapted from: https://vanhunteradams.com/FFT/FFT.html
void FFTfix(fix15 fr[], fix15 fi[]) {
    
    unsigned short m;   // one of the indices being swapped
    unsigned short mr;  // the other index being swapped (r for reversed)
    fix15 tr, ti;       // for temporary storage while swapping, and during iteration
    
    int i, j;           // indices being combined in Danielson-Lanczos part of the algorithm
    int L;              // length of the FFT's being combined
    int k;              // used for looking up trig values from sine table

    int istep;          // length of the FFT which results from combining two FFT's
    
    fix15 wr, wi;       // trigonometric values from lookup table
    fix15 qr, qi;       // temporary variables used during DL part of the algorithm
    
    // Bit Reversal
    // Adapted from: https://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious

    for (m=1; m> 1) & 0x5555) | ((m & 0x5555) << 1); // swap odd and even bits
        
        mr = ((mr >> 2) & 0x3333) | ((mr & 0x3333) << 2); // swap consecutive pairs
        
        mr = ((mr >> 4) & 0x0F0F) | ((mr & 0x0F0F) << 4); // swap nibbles
        
        mr = ((mr >> 8) & 0x00FF) | ((mr & 0x00FF) << 8); // swap bytes
        
        mr >>= SHIFT_AMOUNT; // shift mr down
        
        if (mr<=m) continue; // don't swap that which has already been swapped

        // swap the bit-reversed indices
        tr = fr[m];
        fr[m] = fr[mr];
        fr[mr] = tr;
        ti = fi[m];
        fi[m] = fi[mr];
        fi[mr] = ti;
    }

    // Danielson-Lanczos 
    // Adapted from: Tom Roberts 11/8/89 and Malcolm Slaney 12/15/94 malcolm@interval.com

    L = 1; // Length of the FFT's being combined (starts at 1)
    k = LOG2_NUM_SAMPLES - 1; // Log2 of number of samples, minus 1

    // While length of the FFT's being combined is less than number of gathered samples
    while (L < NUM_SAMPLES) {
        
        istep = L<<1; // Determine the length of the resulting FFT

        // For each element in the FFTs that are being combined
        for (m=0; m>= 1;                          // divide by two
            wi >>= 1;                          // divide by two

            // i gets the index of one of the FFT elements being combined
            for (i=m; i>1; 
                qi = fi[i]>>1; 

                // compute the new values at each index
                fr[j] = qr - tr;
                fi[j] = qi - ti;
                fr[i] = qr + tr;
                fi[i] = qi + ti;
            }    
        }
        --k;
        L = istep;
    }
}

// In-Place Inverse FFT Function
// Slightly modified from above FFT function
// Adapted from: https://vanhunteradams.com/FFT/FFT.html
void iFFTfix(fix15 fr[], fix15 fi[]) {
    
    unsigned short m;   // one of the indices being swapped
    unsigned short mr;  // the other index being swapped (r for reversed)
    fix15 tr, ti;       // for temporary storage while swapping, and during iteration
    
    int i, j;           // indices being combined in Danielson-Lanczos part of the algorithm
    int L;              // length of the FFT's being combined
    int k;              // used for looking up trig values from sine table

    int istep;          // length of the FFT which results from combining two FFT's
    
    fix15 wr, wi;       // trigonometric values from lookup table
    fix15 qr, qi;       // temporary variables used during DL part of the algorithm
    
    // Bit Reversal
    // Adapted from: https://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious

    for (m=1; m> 1) & 0x5555) | ((m & 0x5555) << 1); // swap odd and even bits
        
        mr = ((mr >> 2) & 0x3333) | ((mr & 0x3333) << 2); // swap consecutive pairs
        
        mr = ((mr >> 4) & 0x0F0F) | ((mr & 0x0F0F) << 4); // swap nibbles
        
        mr = ((mr >> 8) & 0x00FF) | ((mr & 0x00FF) << 8); // swap bytes
        
        mr >>= SHIFT_AMOUNT; // shift mr down
        
        if (mr<=m) continue; // don't swap that which has already been swapped

        // swap the bit-reversed indices
        tr = fr[m];
        fr[m] = fr[mr];
        fr[mr] = tr;
        ti = fi[m];
        fi[m] = fi[mr];
        fi[mr] = ti;
    }

    // Danielson-Lanczos 
    // Adapted from: Tom Roberts 11/8/89 and Malcolm Slaney 12/15/94 malcolm@interval.com

    L = 1; // Length of the FFT's being combined (starts at 1)
    k = LOG2_NUM_SAMPLES - 1; // Log2 of number of samples, minus 1

    // While length of the FFT's being combined is less than number of gathered samples
    while (L < NUM_SAMPLES) {
        
        istep = L<<1; // Determine the length of the resulting FFT

        // For each element in the FFTs that are being combined
        for (m=0; m>= 1;                          // divide by two
            wi >>= 1;                          // divide by two

            // i gets the index of one of the FFT elements being combined
            for (i=m; i bin1) {
            multiplier -= 0.5; // Decrease multiplier

            if (multiplier <= 0.0) { // Floor multiplier at 0
                multiplier = 0.0;
            }

            // Scale frequencies
            fr[x] = multfix15(fr[x], float2fix15(multiplier));
            fi[x] = multfix15(fi[x], float2fix15(multiplier));
        }

        // Assign the reflected frequencies
        fr[lookup_table_size - 1 - x] = fr[x];
        fi[lookup_table_size - 1 - x] = fi[x];
    }
}

// run a high pass filter on the fft
void highPassFilter(){
    float multiplier = 0.0; // Slope of filter

    for (int x = 0; x < lookup_table_size/2; x++){

        if(x < bin1 && x >= bin1 - 2) { // Ramp-up of the slope (cutoff - (multiplier / increment))
            multiplier += 0.5; // Increase multiplier

            if (multiplier >= 1.0) { // Ceiling multiplier at 1
                multiplier = 1.0;
            }

            // Scale frequencies
            fr[x] = multfix15(fr[x], float2fix15(multiplier));
            fi[x] = multfix15(fi[x], float2fix15(multiplier));
        }

        else if(x < bin1) { // Filter out the rest
            multiplier = 0;
            fr[x] = multfix15(fr[x], float2fix15(multiplier));
            fi[x] = multfix15(fi[x], float2fix15(multiplier));
        }

        // Assign the reflected frequencies
        fr[lookup_table_size - 1 - x] = fr[x];
        fi[lookup_table_size - 1 - x] = fi[x];
    }
}

// run a band pass filter on the fft
void bandPassFilter(){

    float multiplier = 0.0; // Slope of filter

    for (int x = 0; x < lookup_table_size/2; x++){

        if (x < bin1 - 2) { // filter out frequencies > 10 Hz below lower cutoff
            multiplier = 0;
            fr[x] = multfix15(fr[x], float2fix15(multiplier));
            fi[x] = multfix15(fi[x], float2fix15(multiplier));
        }

        else if (x < bin1 && x >= bin1 - 2) { // + slope if frequency < 10 Hz below lower cutoff
            multiplier += 0.5; // Increase multiplier

            if (multiplier > 1.0) { // Ceiling multiplier at 1
                multiplier = 1.0;
            }

            // Scale frequencies
            fr[x] = multfix15(fr[x], float2fix15(multiplier));
            fi[x] = multfix15(fi[x], float2fix15(multiplier));

            
        }

        else if (x > bin2 && x <= bin2 + 2) { // - slope if frequency < 10 Hz above upper cutoff
            multiplier -= 0.5; // Increase multiplier

            if (multiplier < 0.0) { // Floor multiplier at 0
                multiplier = 0.0;
            }

            // Scale frequencies
            fr[x] = multfix15(fr[x], float2fix15(multiplier));
            fi[x] = multfix15(fi[x], float2fix15(multiplier));

            
        }

        else if (x > bin2 + 2) { // filter out frequencies > 10 Hz above upper cutoff
            multiplier = 0;
            fr[x] = multfix15(fr[x], float2fix15(multiplier));
            fi[x] = multfix15(fi[x], float2fix15(multiplier));
        }

        // Assign the reflected frequencies
        fr[lookup_table_size - 1 - x] = fr[x];
        fi[lookup_table_size - 1 - x] = fi[x];
    }
}

void scaleWaveTable(){

    for (int x = 0; x < lookup_table_size; x++) {

        fr[x] = multfix15(fourFix15,fr[x]); // Scale by a multiple of 4

        // Check and prevent overflow errors
        if (fix2float15(fr[x]) > amplitude_limit) {
            fr[x] = float2fix15(amplitude_limit);
        }

        else if (fix2float15(fr[x]) < -amplitude_limit) {
            fr[x] = float2fix15(-amplitude_limit);
        }
    }
}

// In-Place Wavetable Filter
void FFTfilter(int type) {

    // Copy wavetable into fr[], setting only the real values
    for (int i=0; i < NUM_SAMPLES; i++) {
        fr[i] = currentWave[i];
        fi[i] = ZeroFix15;
    }

    // Compute the FFT
    FFTfix(fr, fi);

    // Filter the frequency domain output of the FFT
    if (type == 0){ // Low-Pass
        lowPassFilter();
    }

    else if (type == 1) { // High-Pass
        highPassFilter();
    }

    else if (type == 2) { // Band-Pass
        bandPassFilter();
    }

    // Perform inverse FFT on the filtered frequency array
    iFFTfix(fr, fi); // turns fr back into wavetable

    // Scale the output of the inverse 
    scaleWaveTable();
}

// populates the sinewave table for the FFT 
void generateSineWave(){    
    for (int jj = 0; jj < NUM_SAMPLES; jj++) {  
        fft_sinewave[jj] = float2fix15(sin(6.283 * ((float) jj) / (float)NUM_SAMPLES)); 
    }   
}

////////////////////////////
/////// END OF FFT /////////
////////////////////////////

bool repeating_timer_callback_core_1(struct repeating_timer *t) {

    LFO_f = fix2float15(getLFO(Frequency));
    phase_incr_LFO = ((LFO_f*two32)/Fs);

    phase_accum_LFO += phase_incr_LFO;

    // multiply cutoff frequencies by LFO in time
    if (getLFO(OnOff) != ZeroFix15) {

        LFO_Shape = fix2int15(getLFO(LFOShape));

        if (getFilter(OnOff) != ZeroFix15){
            filterType = fix2int15(getFilter(Type)) - 1;

            if (LFO_Shape == 1) {
                cutoffFrequency1 = fix2float15(getFilter(cutoff1)) * fix2float15(LFO_sin_freq_table[phase_accum_LFO>>24]);
                cutoffFrequency2 = fix2float15(getFilter(cutoff2)) * fix2float15(LFO_sin_freq_table[phase_accum_LFO>>24]);
            }
            else if (LFO_Shape == 2) {
                cutoffFrequency1 = fix2float15(getFilter(cutoff1)) * fix2float15(LFO_square_freq_table[phase_accum_LFO>>24]);
                cutoffFrequency2 = fix2float15(getFilter(cutoff2)) * fix2float15(LFO_square_freq_table[phase_accum_LFO>>24]);
            }
            else if (LFO_Shape == 3) {
                cutoffFrequency1 = fix2float15(getFilter(cutoff1)) * fix2float15(LFO_triangle_freq_table[phase_accum_LFO>>24]);
                cutoffFrequency2 = fix2float15(getFilter(cutoff2)) * fix2float15(LFO_triangle_freq_table[phase_accum_LFO>>24]);
            }
            else {
                cutoffFrequency1 = fix2float15(getFilter(cutoff1)) * fix2float15(LFO_sawtooth_freq_table[phase_accum_LFO>>24]);
                cutoffFrequency2 = fix2float15(getFilter(cutoff2)) * fix2float15(LFO_sawtooth_freq_table[phase_accum_LFO>>24]);
            }
        
            // recalculate bin numbers
            bin1 = (int) ceil(cutoffFrequency1 * NUM_SAMPLES / Fs);
            bin2 = (int) ceil(cutoffFrequency2 * NUM_SAMPLES / Fs);
            FFTfilter(filterType);
        }

        for (int i = 0; i < NUM_SAMPLES; i++) {
            fin[i] = fr[i];
        }

    }
    else if (getFilter(OnOff) != ZeroFix15) {

        cutoffFrequency1 = fix2float15(getFilter(cutoff1));
        cutoffFrequency2 = fix2float15(getFilter(cutoff2));

        // recalculate bin numbers
        bin1 = (int) ceil(cutoffFrequency1 * NUM_SAMPLES / Fs);
        bin2 = (int) ceil(cutoffFrequency2 * NUM_SAMPLES / Fs);

        filterType = fix2int15(getFilter(Type)) - 1;

        FFTfilter(filterType);

        for (int i = 0; i < NUM_SAMPLES; i++) {
            fin[i] = fr[i];
        }
    }
    else {
        int waveShape = fix2int15(getOscill(Shape));

        if (waveShape == 1) {
            for (int i = 0; i < NUM_SAMPLES; i++) {
                fin[i] = sin_table[i];
            }
        }
        else if (waveShape == 2) {
            for (int i = 0; i < NUM_SAMPLES; i++) {
                fin[i] = triangle_table[i];
            }
        }
        else if (waveShape == 3) {
            for (int i = 0; i < NUM_SAMPLES; i++) {
                fin[i] = square_table[i];
            }
        }
        else {
            for (int i = 0; i < NUM_SAMPLES; i++) {
                fin[i] = sawtooth_table[i];
            }
        }
    }
    
    // check if ociallator is on
    if (getOscill(OnOff) != ZeroFix15){
        ADSR(fix2int15(getOscill(Attack)), fix2int15(getOscill(Decay)), fix2int15(getOscill(Release)));
    }
    else {
        current_amplitude_0 = ZeroFix15;
    }

    // cap the max and min amplitude
    if (current_amplitude_0 >= max_amplitude) {
        current_amplitude_0 = max_amplitude;
    }
    if (current_amplitude_0 < ZeroFix15) {
        current_amplitude_0 = ZeroFix15;
    }

    return true;
}

// This timer ISR is called on core 0
bool repeating_timer_callback_core_0(struct repeating_timer *t) {

    // DDS phase and sine table lookup
    phase_accum_main_0 += phase_incr_main_0;
        DAC_output_0 = fix2int15(multfix15(multfix15(current_amplitude_0,getOscill(Amplitude)), fin[phase_accum_main_0>>24])) + 2048;  

    // Mask with DAC control bits
    DAC_data_0 = (DAC_config_chan_B | (DAC_output_0 & 0xffff))  ;

    // SPI write (no spinlock b/c of SPI buffer)
    spi_write16_blocking(SPI_PORT, &DAC_data_0, 1) ;

    return true;
}

// This is the core 1 entry point. Essentially main() for core 1
void core1_entry() {

    // create an alarm pool on core 1
    alarm_pool_t *core1pool ;
    core1pool = alarm_pool_create(2, 16) ;

    // Create a repeating timer that calls repeating_timer_callback.
    struct repeating_timer timer_core_1;
    
    struct repeating_timer tft_timer_core_1;

    struct repeating_timer hardware_timer_core_1;

    // Negative delay so means we will call repeating_timer_callback, and call it
    // again 25us (40kHz) later regardless of how long the callback took to execute
    // 40 khz -> 10 hz

    // Filter Interrupt Timer
    alarm_pool_add_repeating_timer_us(core1pool, -50, 
        repeating_timer_callback_core_1, NULL, &timer_core_1);

    // tft interrupt
    alarm_pool_add_repeating_timer_us(core1pool, -500, 
        updateTFT, NULL, &tft_timer_core_1);

    // hardware polling interrupt
    alarm_pool_add_repeating_timer_us(core1pool, -100, 
        updateHardware, NULL, &hardware_timer_core_1);

}


// Core 0 entry point
int main() {

    // Initialize stdio/uart (printf won't work unless you do this!)
    stdio_init_all();

    // SPI initalization
    initalizeSPI();

    // TFT initialization
    initalizeTFT();

    // ADC initialize for potentiometer
    initalizeADC();

    // set up the hardware for the buttons and rotary encoder
    setUpHardwareGPIO();
    setUpRotaryEncoder();

    // initalize the TFT text
    setupTFTText(oldSettingGroup);

    // setup the I2C
    setupI2C();

    // Touch Sensor Initialization
    initalizeTouchSensor();

    // set up increments for calculating bow envelope
    attack_inc = divfix(max_amplitude, int2fix15(attack)) ;
    decay_inc =  divfix(max_amplitude, int2fix15(decay)) ;

    // initialization for LFO and 
    phase_incr_main_0 = (noteTable[3]*two32)/Fs;
    phase_incr_LFO = ((LFO_f*two32)/Fs);

    // Build the sine, square, triangle, and sawtooth lookup tables
    buildWaveTables();

    generateSineWave();

    // populate the default wave
    populateWave(square_table);

    // //filterType = 0;
    FFTfilter(filterType);

    // Launch core 1
    multicore_launch_core1(core1_entry);

    // Create a repeating timer that calls 
    // repeating_timer_callback (defaults core 0)
    struct repeating_timer timer_core_0;

    // Negative delay so means we will call repeating_timer_callback, and call it
    // again 25us (40kHz) later regardless of how long the callback took to execute
    add_repeating_timer_us(-50, 
        repeating_timer_callback_core_0, NULL, &timer_core_0);

}