/********************************************************************* * * Laser Harp code using Karplus-strong * Alexander Hatzis and Glenna Zhang * Based on Bruce Land’s Karplus-strong code * ********************************************************************* */ //////////////////////////////////// // clock and protoThreads configure #include "config.h" // threading library #include "pt_cornell_1_2_1.h" // for sine #include //////////////////////////////////// // graphics libraries // SPI channel 1 connections to TFT #include "tft_master.h" #include "tft_gfx.h" // need for rand function #include // need for sin function #include //////////////////////////////////// // === thread structures ============================================ // thread control structs // note that UART input and output are threads static struct pt pt_cmd, pt_tick; // uart control threads static struct pt pt_input, pt_output, pt_DMA_output ; // system 1 second interval tick int sys_time_seconds ; char buffer[60]; // ANALOG INPUTS AND STATE MACHINE VARS // static int adc11[8]; static int prev_adc11[8]; static int adc5; static int lasernumber; // === GCC s16.15 format =============================================== #define float2Accum(a) ((_Accum)(a)) #define Accum2float(a) ((float)(a)) #define int2Accum(a) ((_Accum)(a)) #define Accum2int(a) ((int)(a)) // the native type is _Accum but that is ugly typedef _Accum fixAccum ; #define onefixAccum int2Accum(1) //#define sustain_constant float2Accum(256.0/20000.0) ; // seconds per decay update // === GCC 0.16 format =============================================== #define float2Fract(a) ((_Fract)(a)) #define Fract2float(a) ((float)(a)) // the native type is _Accum but that is ugly typedef _Fract fixFract ; fixFract onefixFract = float2Fract(0.9999); ///#define sustain_constant float2Accum(256.0/20000.0) ; // seconds per decay update /* ====== MCP4822 control word ========================================= bit 15 A/B: DACA or DACB Selection bit 1 = Write to DACB 0 = Write to DACA bit 14 ? Don?t Care bit 13 GA: Output Gain Selection bit 1 = 1x (VOUT = VREF * D/4096) 0 = 2x (VOUT = 2 * VREF * D/4096), where internal VREF = 2.048V. bit 12 SHDN: Output Shutdown Control bit 1 = Active mode operation. VOUT is available. ? 0 = Shutdown the selected DAC channel. Analog output is not available at the channel that was shut down. VOUT pin is connected to 500 k???typical)? bit 11-0 D11:D0: DAC Input Data bits. Bit x is ignored. */ // A-channel, 1x, active #define DAC_config_chan_A 0b0011000000000000 #define DAC_config_chan_B 0b1011000000000000 #define Fs 20000.0 #define two32 4294967296.0 // 2^32 #define num_notes 4 //number of notes we have right now //== Timer 2 interrupt handler =========================================== // actual scaled DAC volatile unsigned int DAC_data; // SPI volatile SpiChannel spiChn = SPI_CHANNEL2 ; // the SPI channel to use volatile int spiClkDiv = 2 ; // 20 MHz max speed for this DAC // wave tables #define sine_table_size 256 volatile fixAccum drive_table[5][sine_table_size]; volatile int current_drive=1, impulse_drive=1 ; // notes C4 C# D Eb E F F# G G# A Bb B4 C5 // 261.6 277.2 293.7 311.1 329.6 349.2 370.0 392.0 415.3 440.0 466.2 493.9 523.25 // matlab length 76.4526 72.1501 68.0967 64.2880 60.6796 57.2738 54.0541 51.0204 // 48.1580 45.4545 42.9000 40.4940 volatile int string_length[] = {76, 68, 60, 57, 51, 45, 40, 38}; // fractional length volatile fixAccum fine_tune[] = {.445, .105, .675, .269, .021, .455, .495, .223}; // = (1-fine_tune)/(1+fine_tune) volatile fixAccum eta [] = {0.384, 0.810, 0.194, 0.576, 0.959, 0.375, 0.337, 0.636}; // = (1-fine_tune)/(1+fine_tune) //volatile fixAccum eta [] = {0.0059, 0.0015, 0.0113, 0.0047, 0.00039, 0.01, 0.01225, .00586 }; //volatile fixAccum eta [] = {0.9941, 0.9985, 0.9887, 0.9953, 0.99961, 0.99, 0.98775, 0.99414 }; //{0.0059, , 0.0015, , 0.0113, 0.0047, , 0.00039,, 0.01, , 0.01225, 0.000526, .00586 }; // index into the note tables volatile int current_note[num_notes] ; int note_list[num_notes][2] ; int note_list_map[8] ; // maximum string length is 320 (min frequency is C2=65 Hz) volatile fixAccum string[num_notes][100] ; // profiling of ISR volatile int isr_time, isr_start_time ; // filter variables for string volatile fixAccum tuning_out[num_notes], last_tune_out[num_notes], last_tune_in[num_notes] ; // low pass coeff units are amount of right-shift in // (((string[ptrin] - string[ptrout])>>lp_coeff) + string[ptrout]) volatile char low_pass_coeff = 1 ; // classic Karplus Strong volatile fixAccum lowpass_out[num_notes] ; // damping coefficient reduction in amp per synthesis sample // implies a e-fold decay time constant of 100 samples // at 20kHz, 5 mSec volatile fixAccum damping_coeff = 0.995 ; volatile char pluck[num_notes] ; volatile fixAccum pluck_amp = 1.0 ; // range 0.0-1.0 // amp of impulse train fed to string volatile fixAccum impulse_amp = 0.0, current_impulse_amp[num_notes], input[num_notes]; // rise/fall time of impulse in samples volatile fixAccum impulse_rise=100, impulse_fall=1000, impulse_rise_inc, impulse_fall_inc ; //volatile int impulse_duration = 100 ; volatile int ptrin[num_notes], ptrout[num_notes] ; // yield time for tempo int tempo_time = 500 ; //mSec // samples generated since last impulse int sample_number[num_notes], local_number[num_notes] ; //============================= void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void) { // time to get into ISR isr_start_time = ReadTimer2(); mT2ClearIntFlag(); int i; for(i = 0; i < num_notes; i++){ sample_number[i]++ ; local_number[i]++ ; // low pass filter lowpass_out[i] = damping_coeff * (((string[i][ptrin[i]] - string[i][ptrout[i]])>>low_pass_coeff) + string[i][ptrout[i]]); // tuning all-pass filter tuning_out[i]= eta[current_note[i]] * (lowpass_out[i] - last_tune_out[i]) + last_tune_in[i] ; // all-pass state vars last_tune_out[i] = tuning_out[i]; last_tune_in[i] = lowpass_out[i]; // sample number measures time since pluck // local number indexes the string length if (local_number[i]==string_length[current_note[i]]){ local_number[i] = 0; } // update impulse drive if (sample_number[i]<(impulse_rise + impulse_fall)){ current_impulse_amp[i] = (sample_number[i]<=impulse_rise)? current_impulse_amp[i] + impulse_rise_inc : current_impulse_amp[i] - impulse_fall_inc ; input[i] = (current_impulse_amp[i]>>10) * drive_table[impulse_drive][local_number[i]] ; //input = drive_table[impulse_drive][local_number] ; } else { input[i] = 0 ; } // string feedback + input string[i][ptrin[i]] = tuning_out[i] + input[i] ; // update and wrap pointers if (ptrin[i]==string_length[current_note[i]]) ptrin[i]=1; else ptrin[i]=ptrin[i]+1; if (ptrout[i]==string_length[current_note[i]]) ptrout[i]=1; else ptrout[i]=ptrout[i]+1; // pluck starts the note timing if (pluck[i]==1) { int j; for (j=0; j<=string_length[current_note[i]]; j++) { string[i][j] = pluck_amp * drive_table[current_drive][j] ; //noise_table[i] ; } // clear the pluck pluck[i] = 0; // reset the pointers ptrin[i] = 1; ptrout[i] = 2; // clear the time counters sample_number[i] = 0 ; local_number[i] = 0 ; // clear continuing input current_impulse_amp[i] = 0; input[i] = 0 ; } } // === Channel A ============= // CS low to start transaction mPORTBClearBits(BIT_4); // start transaction // test for ready //while (TxBufFullSPI2()); // write to spi2 WriteSPI2(DAC_data); // truncate to 12 bits, read table, convert to int and add offset DAC_data = DAC_config_chan_A | (Accum2int((string[0][ptrin[0]] + string[1][ptrin[1]] + string[2][ptrin[2]] + string[3][ptrin[3]])>>2) + 2048) ; //DAC_data = DAC_config_chan_A | (Accum2int(input) + 2048) ; // test for done while (SPI2STATbits.SPIBUSY); // wait for end of transaction // CS high mPORTBSetBits(BIT_4); // end transaction // time to get into ISR is the same as the time to get out so add it again isr_time = ReadTimer2() + isr_start_time ; // - isr_time; } // end ISR TIMER2 // === Read ADC Thread ==================================================== static PT_THREAD (protothread_cmd(struct pt *pt)) { PT_BEGIN(pt); int x; // initialize map, cache, and state variables for(x = 0; x < 8; x++){ prev_adc11[x] = 0; note_list_map[x] = -1; } for(x = 0; x < num_notes; x++){ note_list[x][0] = -1; note_list[x][1] = x; } while(1) { // read this laser’s signal adc11[lasernumber] = ReadADC10(0); if (adc11[lasernumber]<200 && prev_adc11[lasernumber] > 200) { // it’s been plucked! if(note_list_map[7 - lasernumber] == -1){ // we have not plucked this recently (within 4 plucks) int j; int temp; for (j=num_notes-1; j>=0; j--) { if(note_list[j][0] != -1 && j != num_notes-1) { // not last- update maps to new index int prev_index = note_list_map[note_list[j][0]]; note_list_map[note_list[j][0]] = prev_index+1; } else if (j==num_notes-1){ // last note- kicked if(note_list[j][0] != -1) { note_list_map[note_list[j][0]] = -1; } temp = note_list[j][1]; } // shift entries of note_list right one if (j>0){ note_list[j][0] = note_list[j-1][0]; note_list[j][1] = note_list[j-1][1]; } } note_list[0][1] = temp; note_list_map[7-lasernumber] = 0; current_note[note_list[0][1]] = 7 - lasernumber ; } else { // we have plucked the note recently! int index = note_list_map[7-lasernumber]; if (index != 0){ int temp = note_list[index][1]; int j; for (j=index; j>0; j--) { // shift entries of note_list right one if (j>0){ note_list[j][0] = note_list[j-1][0]; note_list[j][1] = note_list[j-1][1]; } // update map int prev_index = note_list_map[note_list[j][0]]; note_list_map[note_list[j][0]] = prev_index+1; } note_list[0][1] = temp; note_list_map[7-lasernumber]=0; } } note_list[0][0] = 7-lasernumber; pluck[note_list[0][1]] = 1; PT_SPAWN(pt, &pt_DMA_output, PT_DMA_PutSerialBuffer(&pt_DMA_output) ); } // update previous ADC value for this laser prev_adc11[lasernumber] = adc11[lasernumber]; // continue to next laser if(lasernumber < 7){ lasernumber++; } else{ lasernumber = 0; } // choose correct mux select bits so we read the correct laser value switch(lasernumber){ case 0: //000 mPORTBToggleBits(BIT_8 | BIT_3 | BIT_10); break; case 1: //001 mPORTBToggleBits(BIT_10); break; case 2: //010 mPORTBToggleBits(BIT_3 | BIT_10); break; case 3: //011 mPORTBToggleBits(BIT_10); break; case 4: //100 mPORTBToggleBits(BIT_8 | BIT_3 | BIT_10); break; case 5: //101 mPORTBToggleBits(BIT_10); break; case 6: //110 mPORTBToggleBits(BIT_3 | BIT_10); break; case 7: //111 mPORTBToggleBits(BIT_10); break; } PT_YIELD_TIME_msec(1) ; } // END WHILE(1) PT_END(pt); } // thread 4 // === TFT Thread ====================================================== // update a 1 second tick counter static PT_THREAD (protothread_tick(struct pt *pt)) { PT_BEGIN(pt); while(1) { // yield time 1 second PT_YIELD_TIME_msec(1000) ; sys_time_seconds++ ; tft_fillRoundRect(0, 0, 50, 200, 1, ILI9340_BLACK); tft_setCursor(0, 0); tft_setTextColor(ILI9340_WHITE); tft_setTextSize(1); tft_writeString("adc11"); tft_setCursor(0, 10); tft_setTextColor(ILI9340_YELLOW); tft_setTextSize(2); sprintf(buffer, "%d", adc11[6]); tft_writeString(buffer); int y = 30; int x; // this is to debug our cache for (x = 0; x < num_notes; x++) { tft_setCursor(0,y); tft_setTextColor(ILI9340_WHITE); tft_setTextSize(1); sprintf(buffer, "%d", note_list[x][0]); tft_writeString(buffer); tft_setCursor(20,y); sprintf(buffer, "%d", note_list[x][1]); tft_writeString(buffer); y += 20; } // NEVER exit while } // END WHILE(1) PT_END(pt); } // thread 4 // === Main ====================================================== // set up UART, timer2, threads // then schedule them as fast as possible int main(void) { // === config the uart, DMA, vref, timer5 ISR ============= PT_setup(); // === setup system wide interrupts ==================== INTEnableSystemMultiVectoredInt(); // 400 is 100 ksamples/sec // 1000 is 40 ksamples/sec // 2000 is 20 ksamp/sec // 1000 is 40 ksample/sec // 2000 is 20 ks/sec OpenTimer2(T2_ON | T2_SOURCE_INT | T2_PS_1_1, 2000); // set up the timer interrupt with a priority of 2 ConfigIntTimer2(T2_INT_ON | T2_INT_PRIOR_2); mT2ClearIntFlag(); // and clear the interrupt flag // SCK2 is pin 26 // SDO2 (MOSI) is in PPS output group 2, could be connected to RB5 which is pin 14 PPSOutput(2, RPB5, SDO2); // control CS for DAC mPORTBSetPinsDigitalOut(BIT_4); mPORTBSetBits(BIT_4); // divide Fpb by 2, configure the I/O ports. Not using SS in this example // 16 bit transfer CKP=1 CKE=1 // possibles SPI_OPEN_CKP_HIGH; SPI_OPEN_SMP_END; SPI_OPEN_CKE_REV // For any given peripherial, you will need to match these SpiChnOpen(spiChn, SPI_OPEN_ON | SPI_OPEN_MODE16 | SPI_OPEN_MSTEN | SPI_OPEN_CKE_REV , spiClkDiv); // the ADC /////////////////////////////////////// // configure and enable the ADC CloseADC10(); // ensure the ADC is off before setting the configuration // define setup parameters for OpenADC10 // Turn module on | ouput in integer | trigger mode auto | enable autosample // ADC_CLK_AUTO -- Internal counter ends sampling and starts conversion (Auto convert) // ADC_AUTO_SAMPLING_ON -- Sampling begins immediately after last conversion completes; SAMP bit is automatically set // ADC_AUTO_SAMPLING_OFF -- Sampling begins with AcquireADC10(); #define PARAM1 ADC_FORMAT_INTG16 | ADC_CLK_AUTO | ADC_AUTO_SAMPLING_ON // // define setup parameters for OpenADC10 // ADC ref external | disable offset test | disable scan mode | do 1 sample | use single buf | alternate mode off #define PARAM2 ADC_VREF_AVDD_AVSS | ADC_OFFSET_CAL_DISABLE | ADC_SCAN_OFF | ADC_SAMPLES_PER_INT_1 | ADC_ALT_BUF_OFF | ADC_ALT_INPUT_OFF // // Define setup parameters for OpenADC10 // use peripherial bus clock | set sample time | set ADC clock divider // ADC_CONV_CLK_Tcy2 means divide CLK_PB by 2 (max speed) // ADC_SAMPLE_TIME_5 seems to work with a source resistance < 1kohm #define PARAM3 ADC_CONV_CLK_PB | ADC_SAMPLE_TIME_15 | ADC_CONV_CLK_Tcy //ADC_SAMPLE_TIME_15| ADC_CONV_CLK_Tcy2 // define setup parameters for OpenADC10 // set AN11 and as analog inputs #define PARAM4 ENABLE_AN11_ANA// pin 24 // define setup parameters for OpenADC10 // do not assign channels to scan #define PARAM5 SKIP_SCAN_ALL // use ground as neg ref for A | use AN11 for input A // configure to sample AN11 SetChanADC10( ADC_CH0_NEG_SAMPLEA_NVREF | ADC_CH0_POS_SAMPLEA_AN11); OpenADC10( PARAM1, PARAM2, PARAM3, PARAM4, PARAM5 ); // configure ADC using the parameters defined above EnableADC10(); // Enable the ADC /////////////////////////////////////////////////////// // === now the threads ==================== // init the display tft_init_hw(); tft_begin(); tft_fillScreen(ILI9340_BLACK); //240x320 vertical display tft_setRotation(1); // Use tft_setRotation(1) for 320x240 srand(1); // init the threads PT_INIT(&pt_cmd); PT_INIT(&pt_tick); // build the sine lookup table // scaled to produce values between 0 and 4095 int i; #define input_lp 6 #define pluck_width 100 #define pluck_position 20 fixAccum dt[sine_table_size]; for (i = 0; i < sine_table_size; i++){ // table 0 is gaussian drive_table[0][i] = float2Accum(2000.0 * exp(-((float)i-pluck_position)*((float)i-pluck_position)/pluck_width) - 1000.0); // table 1 is low pass filtered noise drive_table[1][i] = float2Accum(1023.0*sin(10*3.1416*(float)i/sine_table_size)) - int2Accum(1024); drive_table[2][i] = float2Accum(511.0*(sin(10*3.1416*(float)i/sine_table_size)+ 0.1*sin(10*3.1416*(float)i/sine_table_size))) - int2Accum(1024); // #define RAND_MAX 0x7FFFFFFFu /* max value returned by rand() */ // limit each rand to 0-512, sum four to make 0-2047 Gaussian approx // then subtract 1023 to center drive_table[3][i] = int2Accum((int)((rand()&0x3ff) + (rand()&0x3ff) + (rand()&0x3ff) + (rand()&0x3ff) - 2047)) ; drive_table[4][i] = int2Accum(((int)(rand()&0xfff) - 2047)) ; //drive_table[4][i] = int2Accum(((int)(rand()&0xfff) - 2047)) ; } // Set up analog mux pins mPORTBClearBits(BIT_3 | BIT_10 | BIT_8); mPORTBSetPinsDigitalOut(BIT_3 | BIT_10 | BIT_8); // schedule the threads while(1) { // round robin PT_SCHEDULE(protothread_cmd(&pt_cmd)); PT_SCHEDULE(protothread_tick(&pt_tick)); } } // main ////////////////////////////////////////////////////////