/****************************************************************************** * * * THE MUSIC WAND * * Final Project * * Cornell University ECE 476 * * Nick Hoerter nah35 * * Tom Chatt tjc42 * * * ******************************************************************************/ /**INCLUDE STATEMENTS*/ #include #include #include #include // for sine #define begin { #define end } /**DEFINE STATEMENTS*/ /**Sensor Definitions*/ #define SIpulse 3 //length of SI pulse in clock cycles #define integrateCycles 3 //number of "fast" cycles between read cycles #define fast 200 //MAX value for timer1A during "fast" integrate cycles between read cycles //sets integration time on sensor #define slow 800 //MAX value for timer1A during "slow" read cycles, set to 10KHz for ADC /**Image Processing Definitions*/ #define H 258 //Length of image line to process #define BlineWidth 15 //Nominal width of a black line on an image scan when counting from bottom #define BspaceWidth 15 //Nominal width of a space on an image scan when counting from bottom #define TlineWidth 13 //Nominal width of a black line on an image scan when counting from top #define TspaceWidth 14 //Nominal width of a space on an image scan when counting from top #define d_dt(x2,x1) (x2 - x1) //(non-normalized) derivative macro for peak-picking algorithm #define ppInc 3 //Smoothing increment for peak-picking algorithm #define avg(x1,x2,x3,x4) ((int)((int)(x1) + (int)(x2) + (int)(x3) + (int)(x4))>>2) //4-term average macro #define HiTH 200 //Trough pixel value high threshold #define LowTH 30 //Peak pixel value low threshold #define deltaH 15 //Height difference threshold for note trough validation #define deltaW 21 //Width threshold for determining whether a trough is spurious #define maxCounter 11 //Note detection state machine counter cutoff //note detection state machine states #define start 0 #define foundNote 1 #define playNote 2 #define stillOnNote 3 #define maybeDone 4 //dds definitions //envelope shaping variables #define AttackTarget 0xFF00 #define AttackIncrement 0x7F80 #define DecayTarget 0xAF80 #define DecayDecrement 0x000E #define SustainTarget 0x0005 #define SustainDecrement 0x0002 #define ReleaseDecrement 0x0001 //Envelope state machine state names #define Attack 1 #define Decay 2 #define Sustain 3 #define Release 4 #define NoSound 5 /**VARIABLE DECLARATIONS*/ /**Sensor Variables*/ unsigned char clock; //keeps track of sensor clock high or low int clockCounter; //counts sensor clock periods char read; //0 during integrate cycles, 1 during read cycles char SIcount; //counts out SI pulse to initiate integration and output on sensor char pixelsReady; //flag that pixel array is ready int integrateCounter; //counts number of fast integration cycles between read cycles unsigned char tempPixel; //read in raw pixel values char scanReady; //flag that image processing and note playing is done, time to scan next line of pixels /**Image Processing Variables*/ char startNote; //flag to start DDS char playingNote; //flag that note is playing char noteOn; //variable to hold the location of the note 0-10 from top of staff to bottom, 11 represents no note char imline[H]; //hold pixel values /**Note Detection State Machine Variables*/ char noteToPlay; //variable to hold the location of the note to be played after detected by the state machine char prevNote[5] = {11,11,11,11,11}; //hold buffer of 5 scans to increase note detection accuracy char counter0; //count repeats in prevNote array char counter1; //count repeats in prevNote array char noteState; //state machine variable /**Additive constants for relative positioning of staff. Names represent the number * of space widths and line widths. * Ex. SSL represents the sum of two space widths and one line width * T or B at the beginning of the variable name indicates that the variable is used * when calculating position from the top or bottom of the staff, respectively * These variables are used to eliminate repeated additions during image processing */ char TSL; char TSSL; char TSSLL; char TSSSLL; char TSSSLLL; char TSSSSLLL; char TSSSSLLLL; char TSSSSSLLLL; char BSL; char BSSL; char BSSLL; char BSSSLL; char BSSSLLL; char BSSSSLLL; char BSSSSLLLL; char BSSSSSLLLL; /**Hyperterm Interface Variables*/ char clef[8]; //temp variable to hold the chosen clef, printed in hyperterm char r_char; //holds chars as they are read from hyperterm char ks[32]; //temp variable to hold the chosen keysignature, printed in hyperterm char ksVal; //variable to hold a number corresponding to the chosen keysignature char r_buffer[8]; //read buffer char maybeReset[8]; //read buffer for reset command char r_index; //index into r_buffer and maybeReset char clef_char; //variable to hold clef char reset; //flag to denote that a reset has been requested /**DDS Variables*/ unsigned long accumulator1 @0x2f0; //32 bit accumulator1 unsigned char highbyte1 @0x2f3; //the HIGH byte of the accumulator1 variable unsigned long increment1; //increment for accumulator1 char sineTable[256] @0x400; //need loc to avoid glitch char sineEntry1; //temp sinetable entry to be multiplied by the envelope accumulator char note; //index into the inc_table array, denotes not to be played unsigned int EnvelopeAccumulator; //Envelope shape variable unsigned char EnvelopeState; //Envelope state variable //Table of increment values for synthesis of sinusoids of various frequencies eeprom unsigned long inc_table[39] = { 5999691, 6356483, 6734440, 7135143, 7559142, 8008568, 8484794, 8989195, 9523832, 10090081, 10690002, 11325657, 11999108, 12713103, 13469017, 14269599, 15118285, 16017136, 16969588, 19047665, 20180162, 21380691, 22652001, 23998903, 25425519, 26938035, 28539199, 30236570, 32034271, 33939175, 35957466, 38096016, 40361010, 42760694, 45304002, 47997806, 50851726, 53875383, 57079085 }; //global loop index var unsigned int i; /**FUNCTION DECLARATIONS*/ void initialize(void); //init all variables, ports, and set up interrupts //image processing functions void proc_image(void); //process the line of pixels //hyperterm functions void read_terminal(void); //reads input to hyperterm void promptUser(void); //prompts user for clef and keysignature before scanning //dds functions int multfrac(int a, int b); //multiply a, an 8 bit char, by b, representing an 8 bit fixed point fraction //********************************************************** //UART character-ready ISR //used to read the reset command from hyperterm interrupt [USART_RXC] void uart_rec(void) begin r_char=UDR; //get a char UDR=r_char; //then print it //Implement "backspace" if (r_char == '\b') begin if (r_index > 0) begin r_index--; putchar(' '); putchar('\b'); end end if (r_char != '\r') r_buffer[r_index++]=r_char;//build the input string else begin putchar('\n'); //use putchar to avoid overwrite r_buffer[r_index]=0x00; //zero terminate sscanf(r_buffer,"%s",maybeReset); //scan r_buffer into maybeReset variable if(maybeReset[0] == 'r' && maybeReset[1] == 'e' && maybeReset[2] == 's' && maybeReset[3] == 'e' && maybeReset[4] == 't' && maybeReset[5] == 0) begin //reset has been requested reset=1;//signal reset UCSRB.7=0;//stop rec ISR end else begin //no reset requested for(r_index = 0; r_index < 8; r_index++) r_buffer[r_index] = 0; //if not reset, clear buffer r_index = 0; end end end /* timer0 overflow ISR. * Handle all DDS. * Add necessary increments to accumulators. * Shape amplitude of output using envelope to create piano sound. * Contains envelope state machine. * Output sinewave outputs to PORTB. */ interrupt [TIM0_OVF] void sgen(void) { if(playingNote == 1) begin //increment accumulators accumulator1 = accumulator1 + increment1; sineEntry1 = sineTable[highbyte1]; //retrieve correct sineTable entry PORTB = (char)90 + (char)(0x000000ff&multfrac(sineEntry1, EnvelopeAccumulator)); //scale amplitude according to the envelope //Envelope state machine, borrowed from Adam Hart, Morgan Jones, and Donna Wu switch (EnvelopeState) { //if in the attack phase case Attack: if (EnvelopeAccumulator < AttackTarget) { //ramp up the volume EnvelopeAccumulator += AttackIncrement; EnvelopeState = Attack; } else EnvelopeState = Decay; break; //if in the decay phase case Decay: if(EnvelopeAccumulator > DecayTarget) { //decrease the volume EnvelopeAccumulator -= DecayDecrement; EnvelopeState = Decay; } else EnvelopeState = Sustain; break; //if in the sustain phase case Sustain: if(EnvelopeAccumulator > SustainTarget) { //sustain volume EnvelopeAccumulator -= SustainDecrement; EnvelopeState = Sustain; } else EnvelopeState = Release; break; //if in the die phase case Release: if (EnvelopeAccumulator > 0) { //kill volume EnvelopeAccumulator -= ReleaseDecrement; EnvelopeState = Release; } else EnvelopeState = NoSound; break; //if done with note case NoSound: EnvelopeAccumulator = 0; //reset accumulator EnvelopeState = NoSound; //stay here until restarted PORTB = 128; TIMSK = 0b00010000; //turn off overflow interrupt for timer0 TCCR0 = 0b00001001; //turn off fast PWM mode for timer0 playingNote = 0; //no longer playing note break; } end } //end interrupt /*********************************************************/ /*timer 1 compare ISR * Send sensor clock and start signal, and read sensor. * Handles two different cycles for the sensor, an integrate cycle and a read cycle. * Integrate cycle has a faster clock, because clock speed determines the minimum integration time for pixels * Need a minimum of two integration cycles to ensure integration time is set. * Read cycle has a slower clock to ensure ADC values are ready, maximum of 15KHz sensor clock speed */ interrupt [TIM1_COMPA] void timer1A_compare(void) { if(scanReady == 1 && playingNote == 0) begin clock = PIND.5; //record value of clock signal sent to sensor if(read > 0) //read cycle begin OCR1A = slow; //slow clock for A to D conversion if(clock > 0 && clockCounter < 257) clockCounter++; //keep track of clock cycle count for sensor if(clock == 0) //low cycle of sensor clock begin tempPixel = ADCH; //read pixel value imline[clockCounter] = tempPixel; //store ADCSR.6 = 1; //start a new conversion if(clockCounter == 257) //reached end of pixel line begin clockCounter = 0; //reset clockCounter pixelsReady = 1; //set flag for processing read = 0; //go to integrate cycle SIcount = 0; //start SI pulse counter PORTC.7 = 1; //send SI pulse to begin integration integrateCounter = 0; //first integration cycle beginning end end end if (read == 0) //integration cycles begin OCR1A = fast; //fast clock to set integration time if(clock > 0 && clockCounter < 257) clockCounter++;//keep track of clock cycle count for sensor if(clock == 0 && clockCounter == 257) begin integrateCounter++; //keep track of number of integrate cycles clockCounter = 0; SIcount = 0; //start SI pulse PORTC.7 = 1; end if(integrateCounter == integrateCycles) begin read = 1; //start read cycle integrateCounter = 0; end end if(SIcount < SIpulse) SIcount++; //count out SI pulse else if (SIcount == SIpulse) PORTC.7 = 0; //end SI pulse end }//end interrupt /********************************************************* * Main Program Loop * On start, initialize and prompt user for clef and keysignature. * Begin sensor read cycle, process pixels, and decide if note needs to be played. * Contains a state machine that plays a note if the note has been seen in 4 out of the last 5 scans. * Returns to clef and keysig selection if user enters reset command into Hyperterm. */ void main(void) begin initialize(); //init variables promptUser(); //prompt user for clef and keysignature for(r_index = 0; r_index < 8; r_index++) r_buffer[r_index] = 0; //clear r_buffer to use for reset UCSRB.7=1; //start read interrupt for reset command while(1) begin if(reset==1) begin//reset requested initialize();//re-initialize promptUser(); //prompt for clef and keysignature again UCSRB.7=1; //restart read interrupt reset = 0; end else begin if(pixelsReady == 1) //begin pixel processing begin scanReady = 0; //do not scan pixels during processing to maximize processing speed noteOn = 11; //reset noteOn to no note proc_image(); //process pixels //Begin note detect state machine, only play a note if it is contained //in four of the last 5 scans switch(noteState) { case start: //sitting on blank staff lines begin if(noteOn != 11) //note has been seen begin noteState = foundNote; prevNote[3] = noteOn; //add to scan buffer end else noteState = start; break; end case foundNote://note found begin prevNote[4] = noteOn; //add to scan buffer counter0 = 0; counter1 = 0; for (i = 2; i < 5; i++) //look through buffer for four identical notes begin if (prevNote[i] == prevNote[0]) counter0++; else if (prevNote[i] == prevNote[1]) counter1++; end if (counter0 > 2) //four matches to prevNote[0] begin if (prevNote[0] == 11) //all matches were to blank staff lines, restart state machine begin noteState = start; prevNote[0] = 11; prevNote[1] = 11; prevNote[2] = 11; //Re-initialize buffer prevNote[3] = 11; prevNote[4] = 11; end else begin noteState = playNote; //play detected note noteToPlay = prevNote[0]; end end else if (counter1 > 2) //four matches to prevNote[1] begin if (prevNote[1] == 11) //all matches were to blank staff lines, restart state machine begin noteState = start; prevNote[0] = 11; prevNote[1] = 11; prevNote[2] = 11; //Re-initialize buffer prevNote[3] = 11; prevNote[4] = 11; end else begin noteState = playNote; //play detected note noteToPlay = prevNote[1]; end end else begin//no note detected, shift buffer down prevNote[0] = prevNote[1]; prevNote[1] = prevNote[2]; prevNote[2] = prevNote[3]; prevNote[3] = prevNote[4]; noteState = foundNote; end break; end case playNote: begin /** Variable noteOn contains the line or space (numbered 0-10 from * top to bottom) where the note is on the staff * noteOn = some integer in {0,1,...,9,10} * char clef_char holds 'T' for treble clef, 'B' for bass clef * char ksVal holds 1:12 for keySig * Case statement maps noteToPlay to the actual note to be played by DDS * based on key signature and clef. */ switch(noteToPlay) { case 0: // g treble, b bass begin note = ((clef_char == 'T') ? 37 : 18); break; end case 1: // f treble, a bass begin note = ((clef_char == 'T') ? 35 : 16); break; end case 2: // e treble, g bass begin note = ((clef_char == 'T') ? 34 : 14); break; end case 3: // d treble, f bass begin note = ((clef_char == 'T') ? 32 : 12); break; end case 4: // c treble, e bass begin note = ((clef_char == 'T') ? 30 : 11); break; end case 5: // b treble, d bass begin note = ((clef_char == 'T') ? 29 : 9); break; end case 6: // a treble, c bass begin note = ((clef_char == 'T') ? 27 : 7); break; end case 7: // g treble, b bass begin note = ((clef_char == 'T') ? 25 : 6); break; end case 8: // f treble, a bass begin note = ((clef_char == 'T') ? 23 : 4); break; end case 9: // e treble, g bass begin note = ((clef_char == 'T') ? 22 : 2); break; end case 10: // d treble, f bass begin note = ((clef_char == 'T') ? 20 : 0); break; end }//end switch noteOn //Look at key signature and sharp/flat notes if necessary switch(ksVal) { case 1: //C major: No flats or sharps break ; case 2: //G major: F's are sharp begin if (note == 0 || note == 12 || note == 23 || note == 35) note++; //sharp F's break; end case 3: //D major: F's and C's are sharp begin if (note == 0 || note == 12 || note == 23 || note == 35) note++; //sharp F's if (note == 7 || note == 30) note++; //sharp C's break; end case 4: //A Major: F's, C's, and G's are sharp begin if (note == 0 || note == 12 || note == 23 || note == 35) note++; //sharp F's if (note == 7 || note == 30) note++; //sharp C's if (note == 2 || note == 14 || note == 25 || note == 37) note++; //sharp G's break; end case 5: //E Major: F's, C's, G's, and D's are sharp begin if (note == 0 || note == 12 || note == 23 || note == 35) note++; //sharp F's if (note == 7 || note == 30) note++; //sharp C's if (note == 2 || note == 14 || note == 25 || note == 37) note++; //sharp G's if (note == 9 || note == 20 || note == 32) note++; //sharp D's break; end case 6: //B Major: F's, C's, G's, D's, and A's are sharp begin if (note == 0 || note == 12 || note == 23 || note == 35) note++; //sharp F's if (note == 7 || note == 30) note++; //sharp C's if (note == 2 || note == 14 || note == 25 || note == 37) note++; //sharp G's if (note == 9 || note == 20 || note == 32) note++; //sharp D's if (note == 4 || note == 16 || note == 27) note++; //sharp A's break; end case 7: //F Major: B's are flat begin if (note == 6 || note == 18 || note == 29) note--; //flat B's break; end case 8: //B Flat Major: B's and E's are flat begin if (note == 6 || note == 18 || note == 29) note--; //flat B's if (note == 11 || note == 22 || note == 34) note--; //flat E's break; end case 9: //E Flat Major: B's, E's, and A's are flat begin if (note == 6 || note == 18 || note == 29) note--; //flat B's if (note == 11 || note == 22 || note == 34) note--; //flat E's if (note == 4 || note == 16 || note == 27) note--; //flat A's break; end case 10: //A Flat Major: B's, E's, A's, and D's are flat begin if (note == 6 || note == 18 || note == 29) note--; //flat B's if (note == 11 || note == 22 || note == 34) note--; //flat E's if (note == 4 || note == 16 || note == 27) note--; //flat A's if (note == 9 || note == 20 || note == 32) note--; //flat D's break; end case 11: //D Flat Major: B's, E's, A's, D's, and G's are flat begin if (note == 6 || note == 18 || note == 29) note--; //flat B's if (note == 11 || note == 22 || note == 34) note--; //flat E's if (note == 4 || note == 16 || note == 27) note--; //flat A's if (note == 9 || note == 20 || note == 32) note--; //flat D's if (note == 2 || note == 14 || note == 25 || note == 37) note--; //flat G's break; end case 12: //G Flat Major: B's, E's, A's, D's, G's, and C's are flat begin if (note == 6 || note == 18 || note == 29) note--; //flat B's if (note == 11 || note == 22 || note == 34) note--; //flat E's if (note == 4 || note == 16 || note == 27) note--; //flat A's if (note == 9 || note == 20 || note == 32) note--; //flat D's if (note == 2 || note == 14 || note == 25 || note == 37) note--; //flat G's if (note == 7 || note == 30) note--; //flat C's break; end } //end switch ksVal startNote = 1; //flag to start DDS printf("NotePlayed: %d \r\n", noteToPlay); //print which note has been played for debugging noteState = maybeDone; //enter intermediate state break; end case maybeDone: //intermediate state, stay here until staff lines are detected. //prevents playing double notes begin counter0 = 0; prevNote[4] = noteOn; for (i = 0; i < 5; i++) //scan through buffer looking for staff lines begin if (prevNote[i] == 11) counter0++; end if (counter0 > 2) //found four scans with no note (just staff lines) begin noteState = start; prevNote[0] = 11; prevNote[1] = 11; prevNote[2] = 11; //Reinitialize buffer prevNote[3] = 11; prevNote[4] = 11; end else begin //still see note, shift buffer down prevNote[0] = prevNote[1]; prevNote[1] = prevNote[2]; prevNote[2] = prevNote[3]; prevNote[3] = prevNote[4]; noteState = maybeDone; end break; end } pixelsReady = 0; //pixels are processed end //end if pixelsReady if(startNote == 1) //note detected, start DDS begin TIMSK = 0b00010001; //turn on t0 overflow interrupt TCCR0 = 0b01001001; //turn on t0 fast PWM mode EnvelopeState = Attack; //init envelope EnvelopeAccumulator = 0; PORTB = 90; //init PORTB startNote = 0; //to prevent note from being started twice increment1= inc_table[note]; //pull out correct increment playingNote = 1; end end end //end while end //end main /********************************************************* * initialize all variables, ports, and control registers */ void initialize(void) { //INIT TIMERS //enable output compare match interrupt enable for t1A and t1B TIMSK = 0b00010000; //set timer1 to CTC modes, toggle OC1A on compare match, prescalar to 1 TCCR1A = 0b01000000; TCCR1B = 0b00001001; OCR1A = fast; //set up frequency for timer1A clock fclk = 16Mhz/(2*Prescalar*(1+OCR0) //turn on timer 0 overflow ISR TCCR0 = 0b00000001; //INIT VARIABLES //sensor variables clock = 0; //clock starts low clockCounter = 0; read = 0; SIcount = 0; pixelsReady = 0; scanReady = 0; //image processing variables startNote = 0; playingNote = 0; noteState = start; noteOn = 11; //Calculate additive constants TSL = TlineWidth + TspaceWidth; TSSL = TlineWidth + TspaceWidth*2; TSSLL = TlineWidth*2 + TspaceWidth*2; TSSSLL = TlineWidth*2 + TspaceWidth*3; TSSSLLL = TlineWidth*3 + TspaceWidth*3; TSSSSLLL = TlineWidth*3 + TspaceWidth*4+2; TSSSSLLLL = TlineWidth*4 + TspaceWidth*4+1; TSSSSSLLLL = TlineWidth*4 + TspaceWidth*5; BSL = BlineWidth + BspaceWidth; BSSL = BlineWidth + BspaceWidth*2; BSSLL = BlineWidth*2 + BspaceWidth*2; BSSSLL = BlineWidth*2 + BspaceWidth*2; BSSSLLL = BlineWidth*3 + BspaceWidth*3; BSSSSLLL = BlineWidth*3 + BspaceWidth*4 + 2; BSSSSLLLL = BlineWidth*4 + BspaceWidth*4; BSSSSSLLLL = BlineWidth*4 + BspaceWidth*5; //hyperterm variables r_char = 0; r_index = 0; ksVal = 0; clef_char = 0; reset = 0; //dds variables //init the sine table //sine table contains four harmonics with decreasing amplitude with higher frequency //this adds to piano-like tone. //values borrowed from Adam Hart, Morgan Jones, and Donna Wu for (i=0; i<256; i++) begin sineTable[i] = (char)((40.0 * sin(6.283*((float)i)/256.0)) +(12.0 * sin(6.283*(2*(float)i)/256.0)) +(10.0 * sin(6.283*(3*(float)i)/256.0)) + (8.0 * sin(6.283*(4*(float)i)/256.0))); end increment1 = 0; accumulator1 = 0; EnvelopeState = Attack; EnvelopeAccumulator = 0; note= 25; //arbitrary note choice //fire up the interrupts //INIT ADC //MOUNT AREF JUMPER!!!!!!!!!! ADMUX = 0b00100000; //set ADC for external Vref, PORTA.0 for input ADCSR = 0b10000111; //enable ADC, and set to slowest speed //INIT PORTS DDRD = 0xff; //output for timer1, OC1A comes out of pin D.5 DDRC = 0xff; //output for SI pulse, SI pulse comes out of pin C.7 DDRA = 0x00; //input for ADC, input is pin A.0 DDRB = 0xff; //output for dds, uses all pins PORTB = 0x00; //init port b to output 0 //INIT UART UCSRB = 0x18; UBRRL = 103; //fire up interrupts #asm sei #endasm }//end initialize /************************************************************ *Prompt the user in hyperterm to input clef and key signature. *Set variables to retrieve proper notes during scan */ void promptUser(void){ printf("Choose clef (B for bass clef, T for treble clef):\n\r"); read_terminal(); while(r_buffer[0] != 'B' && r_buffer[0] != 'T' || r_buffer[1] != 0) //ERROR CHECK begin printf("Invalid clef. Please select again.\n\r"); read_terminal(); end if(r_buffer[0] == 'B') //clef is bass begin sprintf(clef, "bass"); //prepare to echo bass to user clef_char = 'B'; //set clef_char to read when note is to be played end else //clef is treble begin sprintf(clef,"treble"); //prepare to echo treble to user clef_char = 'T'; //set clef_char to read when note is to be played end printf("Select key signature: (choose a number 1 - 12)\n\r" "1. C Major (a minor): O flats, 0 sharps\n\r" "2. G Major (e minor): 1 sharp\n\r" "3. D Major (b minor): 2 sharps\n\r" "4. A Major (f sharp minor): 3 sharps\n\r" "5. E Major (c sharp minor): 4 sharps\n\r" "6. B Major (g sharp minor): 5 sharps\n\r" "7. F Major (d minor): 1 flat\n\r" "8. B flat Major (g minor): 2 flats\n\r" "9. E flat Major (c minor): 3 flats\n\r" "10. A flat major (f minor): 4 flats\n\r" "11. D flat major (b flat minor): 5 flats\n\r" "12. G flat major (e flat minor): 6 flats\n\r" "Not sure what a key signature is?\n\r" "Go to the product documentation to learn more...\n\r"); read_terminal(); while (atoi(r_buffer) < 1 || atoi(r_buffer) > 12) //ERROR CHECK begin printf("Invalid choice. Please select again.\n\r"); read_terminal(); end ksVal = atoi(r_buffer); //read keysignature choice as a number switch(ksVal) //prepare to echo keysignature choice to user begin case 1: begin sprintf(ks,"C Major (a minor)"); break; end case 2: begin sprintf(ks,"G Major (e minor)"); break; end case 3: begin sprintf(ks,"D Major (b minor)"); break; end case 4: begin sprintf(ks,"A Major (f sharp minor)"); break; end case 5: begin sprintf(ks,"E Major (c sharp minor)"); break; end case 6: begin sprintf(ks,"B Major (g sharp minor)"); break; end case 12: begin sprintf(ks,"G flat major (e flat minor)"); break; end case 11: begin sprintf(ks,"D flat major (d flat minor)"); break; end case 10: begin sprintf(ks,"A flat major (f minor)"); break; end case 9: begin sprintf(ks,"E flat major (c minor)"); break; end case 8: begin sprintf(ks,"B flat major (g minor)"); break; end case 7: begin sprintf(ks,"F major (d minor)"); break; end end printf("Reading in %s on %s: Please Begin Scanning Now.\n\r",ks,clef); //echo keysignature and clef to user printf("Type \"reset\" to reset scanner.\n\r"); scanReady = 1; //ready to scan music } //end promptUser /******************************************************************* *Read input from hyperterminal into r_buffer when user inputs clef and keysignature */ void read_terminal(void) { for(r_index = 0; r_index < 8; r_index++) r_buffer[r_index] = 0; //clear r_buffer r_index = 0; //reset index r_char = 0; //clear temp read char while(r_char == 0) //read until user is done begin if(UCSRA.7 == 1) //input at terminal, read is ready begin r_char = getchar(); //get a char putchar(r_char); //echo //Implement "backspace" if (r_char == '\b') begin if (r_index > 0) begin r_index--; //move back in buffer r_buffer[r_index] = 0; //clear last char putchar(' '); //print a space to clear last entry putchar('\b'); //clear that space end r_char = 0; //entry not done, loop again end else if(r_char != '\r') begin r_buffer[r_index] = r_char; //add char to buffer r_index++; r_char = 0; //entry not done, loop again end else begin //entry done putchar('\n'); //use putchar to avoid overwrite r_buffer[r_index]=0x00; //zero terminate end end end r_char = 0; //reset r_char r_index = 0; //reset index }//end read_terminal void proc_image(void){ int i,j = 0; //Loop index vars signed char D, prevD = 0; //Temp var for derivative in peak-picking algorithm char D_startIndex, prevD_startIndex; //Indices used for derivative char tempMin, tempMax, tempIndex = 0; //Temp vars for local min/max finding char peaks[2][4] = {{0,0,0,0},{0,0,0,0}}; //Save peak values and indices char troughs[2][5] = {{255,255,255,255,255},{0,0,0,0,0}};//Save trough values and indices char maxTroughVal = 255; //Temp var for finding minimum trough vals char maxTroughValIndex = 0; //Temp var for finding minimum trough vals char Qval = 0; //Quantization value. Changes throughout signed char notePresentFlag = -1; //Flags a trough if it is a note trough char w1,w2,w3,w4,w5 = 0; //Stores the widths of all troughs char minTroughVal = 255; //Temp var for finding minimum trough vals char chunkStart0, chunkStart1, chunkStart2, chunkStart3, chunkStart4 = 0; //Store start and end of char chunkEnd0, chunkEnd1, chunkEnd2, chunkEnd3, chunkEnd4 = 0; // quantized "black" chunks char staffBottom = 0; //Absolute position of staff bottom char staffTop = 0; //Absolute position of staff top //Note: array imline holds the current line of data to be processed /*************************************************************************** ********************************Peak-Picking******************************** ************************************************************************** */ i = 16 + ppInc; //Skip the first few pixels which are always bad data prevD = d_dt(imline[16+ppInc],imline[16]); prevD_startIndex = 16; while (i < H) //Iterate through array and calculate derivatives begin if(i+ppInc >= H) break; while (D == 0 && i+ppInc < H) begin //Derivative is zero, calculate the next one D = d_dt(imline[i+ppInc],imline[i]); D_startIndex = i; i = i + ppInc; end if (prevD < 0 && D > 0) begin //Found a trough //Now go back and find the exact index and value of the trough tempMin = imline[prevD_startIndex]; tempIndex = prevD_startIndex; for (j = ++prevD_startIndex; j < D_startIndex + ppInc + 1; j++) begin if (imline[j] < tempMin) begin tempMin = imline[j]; tempIndex = j; end end //Insert trough into troughs array if (tempMin < maxTroughVal) begin troughs[0][maxTroughValIndex] = tempMin; troughs[1][maxTroughValIndex] = tempIndex; end //Find new maximum value in troughs array maxTroughVal = troughs[0][0]; maxTroughValIndex = 0; for (j = 1; j < 5; j++) begin if (troughs[0][j] > maxTroughVal) begin maxTroughVal = troughs[0][j]; maxTroughValIndex = j; end end end //Reset variables for next iteration prevD = D; prevD_startIndex = D_startIndex; D = 0; end //end while /** We should now have the 5 deepest troughs. * Now sort the troughs array according to the minTroughValIndex */ //Linear sort - slow, but there are only 5 values for (i = 0; i < 4; i++) begin for (j = i+1; j < 5; j++) begin if (troughs[1][j] < troughs[1][i]) begin //Swap row0 vals troughs[0][i] = troughs[0][i] ^ troughs[0][j]; troughs[0][j] = troughs[0][i] ^ troughs[0][j]; troughs[0][i] = troughs[0][i] ^ troughs[0][j]; //Swap row1 vals troughs[1][i] = troughs[1][i] ^ troughs[1][j]; troughs[1][j] = troughs[1][i] ^ troughs[1][j]; troughs[1][i] = troughs[1][i] ^ troughs[1][j]; end end end /** Now iterate through the data between troughs to find the peaks */ for (i = 0; i < 4; i++) begin tempMax = 0; tempIndex = 0; for (j = troughs[1][i]; j < troughs[1][i+1]; j++) begin if (imline[j] > tempMax) begin tempMax = imline[j]; tempIndex = j; end end peaks[0][i] = tempMax; peaks[1][i] = tempIndex; end /** Now we need to look at the values of the peaks and troughs to make sure that * the scanner is not reading all white or all black (i.e, not on paper) */ if (avg(peaks[0][0],peaks[0][1],peaks[0][2],peaks[0][3]) >= LowTH && avg(troughs[0][1],troughs[0][2],troughs[0][3],troughs[0][4]) <= HiTH) begin /**Now we need to look at the troughs and peaks to see if there are any irregularites * that might be caused by a note * Check to see if one trough has height less than avg(trough heights) - deltaH*/ for (j = 0; j < 5; j++) begin if (troughs[0][j] < minTroughVal) begin minTroughVal = troughs[0][j]; notePresentFlag = j; //If one is found, flag it end end //Make sure that a flagged trough is more than deltaH below the average height. //If not, unflag it switch(notePresentFlag) begin case 0: begin if (avg(troughs[0][1],troughs[0][2],troughs[0][3],troughs[0][4]) - deltaH < troughs[0][0]) notePresentFlag = -1; break; end case 1: begin if (avg(troughs[0][0],troughs[0][2],troughs[0][3],troughs[0][4]) - deltaH < troughs[0][1]) notePresentFlag = -1; break; end case 2: begin if (avg(troughs[0][0],troughs[0][1],troughs[0][3],troughs[0][4]) - deltaH < troughs[0][2]) notePresentFlag = -1; break; end case 3: begin if (avg(troughs[0][0],troughs[0][1],troughs[0][2],troughs[0][4]) - deltaH < troughs[0][3]) notePresentFlag = -1; break; end case 4: begin if (avg(troughs[0][0], troughs[0][1],troughs[0][2],troughs[0][3]) - deltaH < troughs[0][4]) notePresentFlag = -1; break; end end if (notePresentFlag > -1) //We might be on a note, so calculate peak widths to check begin w1 = (peaks[1][0] - troughs[1][0])<<1; w2 = peaks[1][1] - peaks[1][0]; w3 = peaks[1][2] - peaks[1][1]; w4 = peaks[1][3] - peaks[1][2]; w5 = (troughs[1][4] - peaks[1][3])<<1; end switch(notePresentFlag) //To get good quantization of a deep trough, we need to flatten the bottom of the deep trough { case -1: break; case 0: begin //Verify that we have a note by making sure the trough is wider than normal if (w1 > avg(w2,w3,w4,w5)) //Trough is wider than normal - this is most likely a note begin troughs[0][0] = troughs[0][1]; end else //Trough is too narrow to be a note notePresentFlag = -1; break; end case 1: begin //Verify that we have a note by making sure the trough is wider than normal if (w2 > avg(w1,w3,w4,w5)) //Trough is wider than normal - this is most likely a note begin troughs[0][1] = (troughs[0][0] + troughs[0][2])>>1; end else //Trough is too narrow to be a note notePresentFlag = -1; break; end case 2: begin //Verify that we have a note by making sure the trough is wider than normal if (w3 > avg(w1,w2,w4,w5)) //Trough is wider than normal - this is most likely a note begin troughs[0][2] = (troughs[0][1] + troughs[0][3])>>1; end else //Trough is too narrow to be a note notePresentFlag = -1; break; end case 3: begin //Verify that we have a note by making sure the trough is wider than normal if (w4 > avg(w1,w2,w3,w5)) //Trough is wider than normal - this is most likely a note begin troughs[0][3] = (troughs[0][2] + troughs[0][4])>>1; end else //Trough is too narrow to be a note notePresentFlag = -1; break; end case 4: begin //Verify that we have a note by making sure the trough is wider than normal if (w5 > avg(w1,w2,w3,w4)) //Trough is wider than normal - this is most likely a note begin troughs[0][4] = troughs[0][3]; end else //Trough is too narrow to be a note notePresentFlag = -1; break; end } // Peak-picking done /**************************************************************************** ************************NORMALIZE TROUGHS AND QUANTIZE*********************** ****************************************************************************/ // Everything up to beginning of first trough for (i = 0; i < ((int)(troughs[1][0])<<1) - (int)(peaks[1][0]); i++) imline[i] = 0; // First Trough Qval = (int)((int)(troughs[0][0]) + (int)(peaks[0][0]))>>1; //Calculate quantization value j = ((int)(troughs[1][0])<<1) - (int)(peaks[1][0]); if (((int)(troughs[1][0])<<1) > (int)(peaks[1][0])) j = ((int)(troughs[1][0])<<1) - (int)(peaks[1][0]); else j = 0; //Make sure no overflow occurs for (i = j; i < peaks[1][0]; i++) begin if (imline[i] < Qval) imline[i] = 1; else imline[i] = 0; if (imline[i] == 1 && imline[i-1] == 0) chunkStart0 = i; if (imline[i] == 0 && imline[i-1] == 1) chunkEnd0 = i; end if (chunkStart0 == 0) chunkStart0 = j; //Again watch for overflow if (chunkEnd0 == 0) chunkEnd0 = peaks[1][0]; // Second Trough Qval = ((int)((int)((int)(peaks[0][0]) + (int)(peaks[0][1]))>>1) + (int)(troughs[0][1]))>>1; for (i = peaks[1][0]; i < peaks[1][1]; i++) begin if (imline[i] < Qval) imline[i] = 1; else imline[i] = 0; if (imline[i] == 1 && imline[i-1] == 0) chunkStart1 = i; if (imline[i] == 0 && imline[i-1] == 1) chunkEnd1 = i; end if (chunkStart1 == 0) chunkStart1 = peaks[1][0]; if (chunkEnd1 == 0) chunkEnd1 = peaks[1][1]; // Third Trough Qval = ((int)((int)((int)(peaks[0][1]) + (int)(peaks[0][2]))>>1) + (int)(troughs[0][2]))>>1; for (i = peaks[1][1]; i < peaks[1][2]; i++) begin if (imline[i] < Qval) imline[i] = 1; else imline[i] = 0; if (imline[i] == 1 && imline[i-1] == 0) chunkStart2 = i; if (imline[i] == 0 && imline[i-1] == 1) chunkEnd2 = i; end if (chunkStart2 == 0) chunkStart2 = peaks[1][1]; if (chunkEnd2 == 0) chunkEnd2 = peaks[1][2]; // Fourth Trough Qval = ((int)((int)((int)(peaks[0][2]) + (int)(peaks[0][3]))>>1) + (int)(troughs[0][3]))>>1; for (i = peaks[1][2]; i < peaks[1][3]; i++) begin if (imline[i] < Qval) imline[i] = 1; else imline[i] = 0; if (imline[i] == 1 && imline[i-1] == 0) chunkStart3 = i; if (imline[i] == 0 && imline[i-1] == 1) chunkEnd3 = i; end if (chunkStart3 == 0) chunkStart3 = peaks[1][2]; if (chunkEnd3 == 0) chunkEnd3 = peaks[1][3]; // Fifth Trough Qval = (int)((int)(peaks[0][3]) + (int)(troughs[0][4]))>>1; if ((((int)(troughs[1][4])<<1) > (int)(peaks[1][3])) && (((int)(troughs[1][4])<<1) - (int)(peaks[1][3])) < 256) j = (((int)(troughs[1][4])<<1) - (int)(peaks[1][3])); else j = 256; for (i = peaks[1][3]; i < j; i++) begin if (imline[i] < Qval) imline[i] = 1; else imline[i] = 0; if (imline[i] == 1 && imline[i-1] == 0) chunkStart4 = i; if (imline[i] == 0 && imline[i-1] == 1) chunkEnd4 = i; end if (chunkStart4 < 150) chunkStart4 = peaks[1][3]; if (chunkEnd4 < 150) chunkEnd4 = j-1; // Everything after end of fifth trough for (i = (((int)(troughs[1][4])<<1) - (int)(peaks[1][3])); i < H; i++) begin imline[i] = 0; end //================IMAGE QUANTIZED in imline======================= /** LOGICAL STRUCTURE if (first line not very thin) if (first line is very wide) FIRST LINE IS A NOTE SKIP TO END AND START FROM THERE GOING BACKWARDS else FIRST LINE IS TOP STAFF LINE USE RELATIVE POSITIONING FROM THERE else FIRST LINE IS SPURIOUS GO TO NEXT LINE */ if (notePresentFlag > -1) { if (notePresentFlag != 0) //first trough is not a note begin if(w1 < deltaW || troughs[0][0] > 105 || w1 > 50) //First trough is spurious begin if(notePresentFlag == 1) //second trough is a note begin //go look at last trough begin //Go look at last trough if (w5 < deltaW || troughs[0][4] > 105 || (w5 > 50 && notePresentFlag != 4)) begin //last trough is spurious, fourth trough is bottom staff line staffBottom = chunkStart3; end else begin //last trough is bottom staff line staffBottom = chunkStart4; end end end else if(w2 < deltaW || troughs[0][1] > 105 || (w2 > 50 && notePresentFlag != 1)) //Second trough is spurious begin if (notePresentFlag == 2) //Third trough is a note begin //Last trough is bottom staff line staffBottom = chunkStart4; end else //Third trough is top staff line begin staffTop = chunkEnd2; end end else begin //Second trough is top staff line staffTop = chunkEnd1; end end else //First trough is first staff line begin staffTop = chunkEnd0; end end else //First trough is a note, start from end begin if (w5 < deltaW || troughs[0][4] > 105 || (w5 > 50 && notePresentFlag != 4)) begin //Last trough is spurious, go to fourth trough if (w4 < deltaW || troughs[0][3] > 105 || (w4 > 50 && notePresentFlag != 3)) begin //Fourth trough is spurious, third trough is bottom staff line staffBottom = chunkStart2; end else begin //Fourth trough is bottom staff line staffBottom = chunkStart3; end end else begin //Last trough is bottom staff line staffBottom = chunkStart4; end end //Now either the top of the staff is in staffTop or the bottom of the staff is in staffBottom if (staffTop == 0) begin //we have the bottom of the staff in staffBottom. Check relative position of note trough if (troughs[1][notePresentFlag] <= staffBottom + BSL && troughs[1][notePresentFlag] > staffBottom + BlineWidth) noteOn = 10; else if (troughs[1][notePresentFlag] <= staffBottom + BlineWidth && troughs[1][notePresentFlag] > staffBottom) noteOn = 9; else if (troughs[1][notePresentFlag] <= staffBottom && troughs[1][notePresentFlag] > staffBottom - BspaceWidth) noteOn = 8; else if (troughs[1][notePresentFlag] <= staffBottom - BspaceWidth && troughs[1][notePresentFlag] > staffBottom - BSL) noteOn = 7; else if (troughs[1][notePresentFlag] <= staffBottom - BSL && troughs[1][notePresentFlag] > staffBottom - BSSL) noteOn = 6; else if (troughs[1][notePresentFlag] <= staffBottom - BSSL && troughs[1][notePresentFlag] > staffBottom - BSSLL) noteOn = 5; else if (troughs[1][notePresentFlag] <= staffBottom - BSSLL && troughs[1][notePresentFlag] > staffBottom - BSSSLL) noteOn = 4; else if (troughs[1][notePresentFlag] <= staffBottom - BSSSLL && troughs[1][notePresentFlag] > staffBottom - BSSSLLL) noteOn = 3; else if (troughs[1][notePresentFlag] <= staffBottom - BSSSLLL && troughs[1][notePresentFlag] > staffBottom - BSSSSLLL) noteOn = 2; else if (troughs[1][notePresentFlag] <= staffBottom - BSSSSLLL && troughs[1][notePresentFlag] > staffBottom - BSSSSLLLL) noteOn = 1; else if (troughs[1][notePresentFlag] <= staffBottom - BSSSSLLLL && troughs[1][notePresentFlag] > staffBottom - BSSSSSLLLL) noteOn = 0; end else begin //we have the top of the staff in staffTop. Check relative position of note trough if ((troughs[1][notePresentFlag] > (staffTop - TSL)) && (troughs[1][notePresentFlag] <= (staffTop - TlineWidth))) noteOn = 0; else if ((troughs[1][notePresentFlag] > (staffTop - TlineWidth)) && (troughs[1][notePresentFlag] <= staffTop)) noteOn = 1; else if ((troughs[1][notePresentFlag] > staffTop) && (troughs[1][notePresentFlag] <= (staffTop + TspaceWidth))) noteOn = 2; else if ((troughs[1][notePresentFlag] > (staffTop + TspaceWidth)) && (troughs[1][notePresentFlag] <= (staffTop + TSL))) noteOn = 3; else if ((troughs[1][notePresentFlag] > (staffTop + TSL)) && (troughs[1][notePresentFlag] <= (staffTop + TSSL))) noteOn = 4; else if ((troughs[1][notePresentFlag] > (staffTop + TSSL)) && (troughs[1][notePresentFlag] <= (staffTop + TSSLL))) noteOn = 5; else if ((troughs[1][notePresentFlag] > (staffTop + TSSLL)) && (troughs[1][notePresentFlag] <= (staffTop + TSSSLL))) noteOn = 6; else if ((troughs[1][notePresentFlag] > (staffTop + TSSSLL)) && (troughs[1][notePresentFlag] <= (staffTop + TSSSLLL))) noteOn = 7; else if ((troughs[1][notePresentFlag] > (staffTop + TSSSLLL)) && (troughs[1][notePresentFlag] <= (staffTop + TSSSSLLL))) noteOn = 8; else if ((troughs[1][notePresentFlag] > (staffTop + TSSSSLLL)) && (troughs[1][notePresentFlag] <= (staffTop + TSSSSLLLL))) noteOn = 9; else if ((troughs[1][notePresentFlag] > (staffTop + TSSSSLLLL)) && (troughs[1][notePresentFlag] <= (staffTop + TSSSSSLLLL))) noteOn = 10; end } end scanReady = 1; }//end proc_image //fixed point fractional multiply //heavily adapted from Adam Hart, Morgan Jones, and Donna Wu int multfrac(int a,int b) { #asm ;****************************************************************************** ;* ;* FUNCTION ;* mulsu8x8_16 ;* DECRIPTION ;* Signed multiply of a 8-bit char to a fixed 8-bit fraction, outputting a 16-bit fixed. ;* Only returns upper 8 bits of the 16 bit fixed, fraction is unnecessary ;* USAGE ;* r30.0 = r22* 0.r23 ;****************************************************************************** LDD R22,Y+2 ;load a LDD R23,Y+1 ;load b CLR R31 MULSU R22, R23 MOV R30, R1 #endasm }