CSUSM FM Synth 2017 / Mbed 2 deprecated FMSynthCSUSM

Dependencies:   mbed

Fork of STM32FMSynth by Steven Clark

main.cpp

Committer:
davolfman
Date:
2017-12-15
Revision:
23:7a9ff5230149
Parent:
22:0307adac8c35

File content as of revision 23:7a9ff5230149:

#include "mbed.h"
#include "sintable.h"

AnalogOut outMono(PA_4);//Not labeled in the docs for the f401, but seems to be for all
//AnalogOut DAC1(PA_5);

AnalogIn inVol(PA_0);
AnalogIn inModAmt(PA_1);
//AnalogIn ADC2(PA_2);//these are the uart pins!!
//AnalogIn ADC3(PA_3);//these are the uart pins!!
//AnalogIn ADC4(PA_4);//we're using these for output
//AnalogIn ADC5(PA_5);//we're using these for output
AnalogIn inCarA(PA_6);
AnalogIn inCarD(PA_7);
//AnalogIn ADC8(PB_0);//lets leave the 2 we aren't using in a single port
//AnalogIn ADC9(PB_1);//that way we know there's not ADCs on one of them
AnalogIn inCarS(PC_0);
AnalogIn inCarR(PC_1);
AnalogIn inModA(PC_2);
AnalogIn inModD(PC_3);
AnalogIn inModS(PC_4);
AnalogIn inModR(PC_5);

//BusIn keyBank(PC_10, PC_11, PC_12, PC_13, PD_2, PH_1); old
BusIn keyBank(PH_1, PD_2, PC_13, PC_12, PC_11, PC_10);
BusOut bankSelect(PB_0, PB_1, PB_2, PB_3, PB_4, PB_5, PB_6, PB_7, PB_8);
BusIn numerator(PA_8, PA_9, PA_10, PA_11);
BusIn denominator(PA_12, PA_13, PA_14, PA_15);

//Serial pc(USBTX, USBRX);

Ticker synthesisClock;// this object sets up an ISR to execute every given fraction of a second.

#define numKeys 49// our keyboard includes from two octaves above middle c to two octaves below it

//constants

//These are the pitches of the notes of our keyboard in hz * int16_max / sampling rate(20khz)
const int carrierIncrements[] = {214, 227, 240, 254, 270, 286, 303, 321, 340, 
    360, 381, 404, 428, 454, 481, 509, 540, 572, 606, 642, 680, 720, 763, 809, 
    857, 908, 962, 1019, 1080, 1144, 1212, 1284, 1360, 1441, 1527, 1618, 1714, 
    1816, 1924, 2039, 2160, 2288, 2424, 2568, 2721, 2883, 3055, 3236, 3429};
    
//The maximum value of our envelopes is int16_max
#define attackLimit 0xFFFF

//this gives us a value of pi to multiply things by
#define U_PI 3.14159265358979

//non-constants
//Most of these will be recalculated or reset on every input cycle of the main
//  loop, as appropriate

int FMmult = 1;//The modulator pitch is FMmult * the base carrier pitch for that note
int Volume = 0xffff;//the maximum volume to start, our number format is fixed 16 bits of fractional
//                      inside a 32bit integer usually only in that fractional allowing for
//                      integer multiplications and shifts to recenter instead of floating
//                      -point arithmetic
int modVol = 0x2000;//the amount of modulation to apply, most useful relatively low
int64_t keyboard = 0;//our key state is stored as bit flags in the lower 49 bits of this
int64_t modattack = 0x1ffffffffffff;//similar to keyboard, if the corrsponding bit is
//                                      zero the envelope for that bit is in decay or sustain instead of attack
int64_t carattack = 0x1ffffffffffff;
int carrierPhases[numKeys];//store the phases of the notes in between samplings
int modulatorPhases[numKeys];
int envelopeAmpsC[numKeys];//store the amplitudes of the envelopes in between samplings
int envelopeAmpsM[numKeys];

//the envelope parameters for synthesis are read from these registers
int modA = 0xffff;//modulator attack rate
int modD = 0xffff;//modulator decay rate
int modS = 0;//modulator sustain level
int modR = 0xffff;//modulator release rate
int carA = 0xffff;//carrier attack rate
int carD = 0xffff;//carrier decay rate
int carS = 0;//carrier sustain level
int carR = 0xffff;//carrier release level

///@brief Converts a phase of period 2^16 into a sine value
///@phase The phase to calculate the sine of in phase/65536 * 2 pi radians
///@return the sine as a signed 16 bit fractonal part inside a 32 bit int
int fastSin(const int phase){
    //the middle 12 bits are used to index into a lookuptable of pre computed sines
    int index = (phase & 0x3ffc) >> 2;
    
    //we used linear interpolation given out bottom 2 bits to turn a 2^12 Look Up Table into a 2^14 LUT
    int subindex = phase & 0x3;
    
    //We use mirroring of a quarter wave of sine so we only have to store a quarter of the samples again
    int quadrant = (phase & 0xc000) >> 14;
    int sum = 0;
    switch (quadrant) {//perform the mirroring and add the memebers of the weighted average for interpolation
        case 0:
            sum += (4 - subindex) * sinTable[index];
            sum += subindex * sinTable[index+1];
            break;
        case 1:
            sum += (4 - subindex) * sinTable[1+4095-index];
            sum += subindex * sinTable[4095-index];
            break;
        case 2:
            sum -= (4 - subindex) * sinTable[index];
            sum -= subindex * sinTable[index+1];        
            break;
        case 3:
            sum -= (4 - subindex) * sinTable[1+4095-index];
            sum -= subindex * sinTable[4095-index];
            break;
    }
    sum = sum >> 2;//divide the weighted sum of the neighborign samples by 4
    //to get a weighted average
    
    return sum;
}

///@brief calculates one audio sample given the keyboard state and envelope paramaters and passes it to the DAC
void synthesize(){
    int wave = 0;// holds the sample being constructed.
    int subsignal;// the subsample for one note
    int64_t keymask;// holds a mask of the current note for easy access to keyboard and attack registers
        
    //for all keys
    for(int64_t i = 0; i < numKeys; ++i){
        keymask = 1ll << i;//set the key mask
                
        if(!(keymask & keyboard)){//if the key is NOT pressed
            carattack |= keymask;//allow attack the next time it is
            modattack |= keymask;
            
            //if envelope is still positive, decrement by decay rate
            if(envelopeAmpsC[i] > 0){
                envelopeAmpsC[i] -= carR;
            }
            if(envelopeAmpsM[i] > 0){
                envelopeAmpsM[i] -= modR;
            }
            
        }else{//if the key IS pressed
        
            if(envelopeAmpsC[i] <= 0){//if this key was silent before,
                carrierPhases[i] = 0;//reset the wave states
                modulatorPhases[i] = 0;//this should prevent frequency drift from stopped FM
                envelopeAmpsM[i] = 0;
                envelopeAmpsC[i] = 1;//only do it once
            }

            //if carrier has not left attack phase
            if(keymask & carattack){
                //add attack rate to envelope if not already maximised
                if(envelopeAmpsC[i] < attackLimit ){
                    envelopeAmpsC[i] += carA;
                }else{//otherwise clip to maximum and leave attack pahse
                    envelopeAmpsC[i] = attackLimit;
                    carattack &= ~keymask;
                }
            }else{//if in decay/sustain
                if(envelopeAmpsC[i] > carS){//subtract the decay rate if above sustain level
                    envelopeAmpsC[i] -= carD;
                }
            }
            
            //do all that again for the modulator envelope
            if(keymask & modattack){
                if(envelopeAmpsM[i] < attackLimit){
                    envelopeAmpsM[i] += modA;
                }else{
                    envelopeAmpsM[i] = attackLimit;
                    modattack &= ~keymask;
                }
            }else{
                if(envelopeAmpsM[i] > modS){
                    envelopeAmpsM[i] -= modD;
                }
            }
            
        }
        
        //If this subsignal is not silent
        if(envelopeAmpsC[i] > 0){
            //calculate the new phase of the modulator
            modulatorPhases[i] += (carrierIncrements[i] * FMmult)>> 16;
                
            //get the sine for that phase and scale it by the envelope
            int modulation = (fastSin(modulatorPhases[i]) * envelopeAmpsM[i])>>16;
             
            //scale it again by the modulation amount
            modulation = (modulation * modVol) >> 16;
           
            //calculate the new phase of the carrier, modualting frequency by the modulation
            carrierPhases[i] += carrierIncrements[i] + modulation;
                
            //get the sine for that carrier phase and scale by the envelope
            //additionally divide by 8 to allow 8 notes to play without saturating the DAC
            subsignal = (fastSin(carrierPhases[i]) * envelopeAmpsC[i])>>19;
                
            //add the sample for this note into the overall sample
            wave += subsignal;
        }
        
    }

    //Scale the complete sample by the volume    
    wave = wave * Volume >> 16;

    //clip the sample to within the limits of the DAC if neccessary
    wave = (wave > 32767) ? 32767 : wave;
    wave = (wave < -32768) ? - 32768 : wave;
    
    //Center the waveform within the range of the DAC
    wave += 32768;
    
    //output the sample
    outMono.write_u16(wave);
}


int main() {
    int ratNumer;
    int ratDenom;    
    int64_t keytemp;
    int tempCarA, tempCarD, tempCarR, tempModA, tempModD, tempModR;

    for(int i = 0; i < numKeys; ++i){ //zero out values
        carrierPhases[i] = 0;
        modulatorPhases[i] = 0;
        envelopeAmpsC[i] = 0;
        envelopeAmpsM[i] = 0;
    }
    
    keyBank.mode(PullNone); // we're using external pullup resistors, 
                //and things weren't working this didn't fix it but better safe
                
    synthesisClock.attach(synthesize, 0.00005); //this runs every 50 us or 20khz
    
    while(true){
        ratNumer = 0xf & ~ numerator; //read ratio numerator
        ratDenom = 0xf & ~ denominator; //read ratio denominator
        FMmult = (ratNumer << 16) / ratDenom; //FM multiplier = numerator / denominator
        
        Volume = (int)inVol.read_u16(); //read volume
        
        modVol = (int)inModAmt.read_u16(); //read modulation amount
        
        //ensure we don't divide by zero on any of these

        //read carrier attack
        tempCarA = inCarA.read_u16();
        if(! tempCarA)
            carA = 0xffff;
        else
            carA = 0xffff / tempCarA;
        
        //read carrier decay
        tempCarD = inCarD.read_u16();
        if(! tempCarD)
            carD = 0xffff;
        else
            carD = 0xffff / tempCarD;
        
        //read carrier sustain
        carS = (int)inCarS.read_u16();
        
        //read carrier release
        tempCarR = inCarR.read_u16();
        if(! tempCarR)
            carR = 0xffff;
        else
            carR = 0xffff / tempCarR;
        
        //read modulation attack
        tempModA = inModA.read_u16();
        if(! tempModA)
            modA = 0xffff;
        else
            modA = 0xffff / tempModA;
        
        //read modulation decay
        tempModD = inModD.read_u16();
        if(! tempModD)
            modD = 0xffff;
        else
            modD = 0xffff / tempModD;
        
        //read modulation sustain
        modS = (int)inModS.read_u16();
        
        //read modulation release
        tempModR = inModR.read_u16();
        if(! tempModR)
            modR = 0xffff;
        else
            modR = 0xffff / tempModR;
        
        keytemp = 0; //zero the keys before we start ORing on top of everything, 
        //keytemp is a buffer that waits until every key is read, then it pushes 
        //into the 'keyboard' variable
        
        for(int i = 0; i < 9; ++i) {//for all 9 half-octaves of the keyboard
            bankSelect = (~(1 << i)) & bankSelect.mask();//supply power (as ground) to that bank
            wait_us(200); //delay to get to get full reading
            int shiftOffset = 6LL * i; //read the keys in the bank
            
            //assign the bank to its corresponding position in the overall keyboard
            keytemp |= ((~(unsigned long long)keyBank) & (unsigned long long)keyBank.mask()) << (unsigned long long)shiftOffset;
        }
        keytemp >>= 5;//shift 5 over because bank 0 only has 1 key and it is the 6th key (...111111 111111 100000)
        
        keyboard = keytemp;//push our read values once we are complete. 
        //This only takes 1 cycle, especially since we are using 'keyboard' in the ISR
        
        //wait_ms(10);//we've stopped bothering to wait here.  it didn't make a difference
    }
}