| CODE |
/* MIDI Sequencer EE476 Final Project Nicholas Kisler */ #include <90s8515.h> #include <stdio.h> #include <String.h> #include <delay.h> #asm .equ __lcd_port=0x1b #endasm #include <lcd.h> //constants #define NTRK 4 #define NMEAS 2 //keypad modes #define M_NORM 0 #define M_PITCH 1 #define M_VEL 2 #define M_LENGTH 3 #define M_TEMPO 4 //keypad commands for M_NORM mode #define K_BEAT_D 0 #define K_BEAT_U 1 #define K_STOP 2 #define K_PLAY 3 #define K_MEAS_D 4 #define K_MEAS_U 5 #define K_EDIT_PITCH 6 #define K_EDIT_VELOCITY 7 #define K_TRACK_D 8 #define K_TRACK_U 9 #define K_EDIT_LENGTH 10 #define K_EDIT_TEMPO 11 #define K_LOOP 12 #define K_TRIG 13 #define K_PRESET 14 //keypad commands for other modes #define K_OCT_D 12 #define K_OCT_U 13 #define K_CANCEL 14 #define K_ENTER 15 flash unsigned char keytbl[16] = { 0xee,0xed,0xeb,0xe7, 0xde,0xdd,0xdb,0xd7, 0xbe,0xbd,0xbb,0xb7, 0x7e,0x7d,0x7b,0x77 }; //externally modified variables unsigned char pch[NTRK][NMEAS*16]; //contains pitches of notes in each track, if the high bit is set then //the note is held over (ie. no new note is started in that beat) unsigned char vel[NTRK][NMEAS*16]; //contains velocities of notes in each track unsigned char loop[NTRK]; //the loop pointer for each track, sequence will loop when it gets //to this beat unsigned char tempo; //the tempo in beats per minute (bpm) //internally modified variables unsigned char beat[NTRK]; //the next beat of the sequence to play for each track unsigned char buffer[16]; //used to buffer outgoing MIDI bytes unsigned char buf_idx; //used to determine the length of data to send unsigned char cur_track,cur_meas,cur_beat; //the track/measure/beat currently selected by the user unsigned char field[3]; //used to hold digits entered by the user unsigned char field_count; //used to index field[] unsigned char* fieldstr[4]; //a string used to output field[] to the display float flt_temp; //temporary floating point variable used in tempo calculations unsigned char i1; //loop index for timer1 interrupt handler unsigned char prepare; //flag signalling that the main method should prepare the buffer unsigned char play_flag; //flag signalling the main method to start playing unsigned char command; //command from keypad unsigned char state; //state of keypad state machine unsigned char stateFlag; //flag signalling the main method to enter the keypad state machine char key2; //saved key from the keypad state machine char* outstr[7]; //temporary string used by the display unsigned char mode; //the mode of operation unsigned char saved_val; //used by the function that executes keypad commands to save some value //before the user starts editing unsigned char trigger; //flag used by the main method to determine when to turn on/off a note //triggered by the user unsigned char trig_timer; //controls duration of the triggered note void initialize(void); void prepBuffer(void); //used by the main method to prepare the buffer to be output in //the timer1 interrupt handler void formatNote(unsigned char note); //formats a note from a byte into a readable value (ex. "C#4") void refresh(void); //refreshes the LCD screen char getButton(void); //returns the button currently being pressed by the user void stateMachine(void); //keypad scanning state machine void doCommand(void); //executes command output from the keypad state machine char getLength(void); //returns the length of the note currently selected by the user //timer1 interrupt handler: outputs buffer to MIDI, increments and loops the beat for each track, //signals the main method upon completion so that the next values can be entered into the buffer interrupt [TIM1_COMPA] void tim1_cmpA(void) { for(i1=0;i1<buf_idx;i1++) { putchar(buffer[i1]); } for(i1=0;i1<NTRK;i1++) { if (loop[i1] != beat[i1]) beat[i1] = (beat[i1] + 1) & ((NMEAS<<4) - 1); else beat[i1] = 0; } prepare = 1; } //sets stateFlag every 30ms (stateFlag is cleared by main method) interrupt [TIM0_OVF] void t0ovf(void) { TCNT0 = 139; stateFlag = 1; } void prepBuffer(void) { unsigned char trk; unsigned char last_beat; buf_idx = 0; buffer[buf_idx++] = 0x90; //status byte for note on message for(trk=0;trk<NTRK;trk++) { //if the note is not a held note, then sequencer may have to perform an operation if (pch[trk][beat[trk]] < 0x80) { if (beat[trk] == 0) last_beat = loop[trk]; else last_beat = beat[trk] - 1; //cut old note, need to make sure "old" note is not cut on first time if ((vel[trk][last_beat] > 0) && (play_flag == 0)) { buffer[buf_idx++] = pch[trk][last_beat]; buffer[buf_idx++] = 0x00; } //play new note if (vel[trk][beat[trk]] > 0) { buffer[buf_idx++] = pch[trk][beat[trk]]; buffer[buf_idx++] = vel[trk][beat[trk]]; } } } } void formatNote(unsigned char note) { unsigned char oct,rem; note = note & 0x7f; //the high bit is used to keep track of non-MIDI information oct = (char)(note / 12); //each octave is 12 notes rem = note % 12; //the remainder determines which pitch switch(rem) { case 0: sprintf(outstr,"C %-1d",oct); break; case 1: sprintf(outstr,"C#%-1d",oct); break; case 2: sprintf(outstr,"D %-1d",oct); break; case 3: sprintf(outstr,"D#%-1d",oct); break; case 4: sprintf(outstr,"E %-1d",oct); break; case 5: sprintf(outstr,"F %-1d",oct); break; case 6: sprintf(outstr,"F#%-1d",oct); break; case 7: sprintf(outstr,"G %-1d",oct); break; case 8: sprintf(outstr,"G#%-1d",oct); break; case 9: sprintf(outstr,"A %-1d",oct); break; case 10: sprintf(outstr,"A#%-1d",oct); break; case 11: sprintf(outstr,"B %-1d",oct); break; default: sprintf(outstr,"xx%-1d",oct); break; } } void refresh(void) { unsigned char i,j,temp; lcd_clear(); //position field lcd_gotoxy(0,0); sprintf(outstr,"%2d:",(char)(cur_track+1)); lcd_puts(outstr); sprintf(outstr,"%2d:",(char)(cur_meas+1)); lcd_puts(outstr); if (cur_beat < 9) lcd_putsf(" "); sprintf(outstr,"%-2d",(char)(cur_beat+1)); lcd_puts(outstr); //tempo field lcd_gotoxy(11,0); if (mode == M_TEMPO) { lcd_putsf("@"); lcd_puts(fieldstr); lcd_putsf("bpm"); } else { sprintf(outstr," %-3dbpm",tempo); lcd_puts(outstr); } //note info field lcd_gotoxy(0,1); if (mode == M_PITCH) lcd_putsf("@"); else lcd_putsf(" "); formatNote(pch[cur_track][cur_beat + (cur_meas<<4)]); lcd_puts(outstr); lcd_gotoxy(5,1); if (mode == M_VEL) { lcd_putsf("@V="); lcd_puts(fieldstr); } else { lcd_putsf(" V="); sprintf(outstr,"%-3d",vel[cur_track][(char)(cur_beat + (cur_meas<<4))]); lcd_puts(outstr); } lcd_gotoxy(12,1); if (mode == M_LENGTH) { lcd_putsf("@L="); lcd_puts(fieldstr); } else { if (pch[cur_track][cur_beat + (cur_meas<<4)] >= 0x80) { lcd_putsf(" L=--"); } else { lcd_putsf(" L="); temp = getLength(); sprintf(outstr,"%-2d",temp); lcd_puts(outstr); } } //measure overview field for(i=0;i<16;i++) { lcd_gotoxy(i,2); //if note is not a held note if (pch[cur_track][i + (cur_meas<<4)] < 0x80) { //if note is on if (vel[cur_track][i + (cur_meas<<4)] > 0x00) { lcd_putsf("X"); } else { lcd_putsf("-"); } //else note is a held note } else { //if note is on if (vel[cur_track][i + (cur_meas<<4)] > 0x00) { lcd_putsf("x"); } else { lcd_putsf("="); } } } //track overview field lcd_gotoxy(16,3); temp = 0; //checks to see if track is empty or not for(i=0;i<NTRK;i++) { for(j=0;j<(NMEAS<<4);j++) { if (vel[i][j] != 0) temp = 1; } if (temp == 1) lcd_putsf("+"); else lcd_putsf("-"); temp = 0; } //loop pointer portion of measure overview field lcd_gotoxy(loop[cur_track] & 0x0f,3); //go to the position corresponding to the loop pointer //if the loop pointer should be displayed for this measure, display it if ((cur_meas == 0) && ((loop[cur_track]>>4) == 0)) lcd_putsf("|"); else if ((cur_meas == 1) && ((loop[cur_track]>>4) == 1)) lcd_putsf("|"); } //returns length of currently selected note char getLength(void) { unsigned char i,temp; temp = 1; i = (cur_beat + (cur_meas<<4) + 1) & ((NMEAS<<4) - 1); //start on beat after current beat //while beat is part of held note, advance to next beat (max length of 32) while((pch[cur_track][i] >= 0x80) && (temp < 32)) { temp++; i = i+1; if (i==32) i=0; } return temp; } void doCommand(void) { unsigned char pitch,velocity; unsigned char value; unsigned char i; //if a new command is ready, execute it if (command != 0xff) { switch(mode) { case M_NORM: switch(command) { case K_PLAY: play_flag = 1; break; case K_STOP: TCCR1B = 0x00; //stop timer1 //turn notes off putchar(0x90); for(i=0;i<NTRK;i++) { if (beat[i] == 0) value = loop[i]; else value = beat[i] - 1; if (vel[i][value] > 0) { putchar(pch[i][value] & 0x7f); putchar(0x00); } } break; case K_BEAT_U: cur_beat = (cur_beat + 1) & 0x0f; refresh(); break; case K_BEAT_D: cur_beat = (cur_beat - 1) & 0x0f; refresh(); break; case K_MEAS_U: cur_meas = (cur_meas + 1) & (NMEAS-1); refresh(); break; case K_MEAS_D: cur_meas = (cur_meas - 1) & (NMEAS-1); refresh(); break; case K_TRACK_U: cur_track = (cur_track + 1) & (NTRK-1); refresh(); break; case K_TRACK_D: cur_track = (cur_track - 1) & (NTRK-1); refresh(); break; case K_LOOP: loop[cur_track] = cur_beat + (cur_meas<<4); refresh(); break; case K_EDIT_PITCH: //if note is not held, then command is valid if (pch[cur_track][cur_beat + (cur_meas<<4)] < 0x80) { saved_val = pch[cur_track][cur_beat + (cur_meas<<4)]; //save current pitch value mode = M_PITCH; lcd_gotoxy(0,1); refresh(); } break; case K_EDIT_VELOCITY: //if note is not held, then command is valid if (pch[cur_track][cur_beat + (cur_meas<<4)] < 0x80) { saved_val = vel[cur_track][cur_beat + (cur_meas<<4)]; //save current velocity value //initialize values for display field_count = 0; sprintf(fieldstr,"___"); mode = M_VEL; refresh(); } break; case K_EDIT_LENGTH: //if note is not held, then command is valid if (pch[cur_track][cur_beat + (cur_meas<<4)] < 0x80) { //initialize values for display field_count = 0; sprintf(fieldstr,"__"); mode = M_LENGTH; refresh(); } break; case K_EDIT_TEMPO: saved_val = tempo; //save current tempo //initialize values for display field_count = 0; sprintf(fieldstr,"___"); mode = M_TEMPO; refresh(); break; case K_TRIG: trig_timer = 10; //start timer for triggered note, main method will handle from here on break; } //end switch(command) break; case M_PITCH: //parse command as according to specification for keypad in pitch mode if (command <= 11) { pitch = (char)(pch[cur_track][cur_beat + (cur_meas<<4)] % 12); //get the current pitch value //if the pitch selected is in the proper range, modify the pch array if ((pch[cur_track][cur_beat + (cur_meas<<4)] + (command-pitch)) <= 127) { pch[cur_track][cur_beat + (cur_meas<<4)] += (command-pitch); //add/sub the difference refresh(); } } else if (command == K_OCT_U) { //if the octave selected is in the proper range, modify the pch array if (pch[cur_track][cur_beat + (cur_meas<<4)] <= 115) { pch[cur_track][cur_beat + (cur_meas<<4)] += 12; refresh(); } } else if (command == K_OCT_D) { //if the octave selected is in the proper range, modify the pch array if (pch[cur_track][cur_beat + (cur_meas<<4)] >= 12) { pch[cur_track][cur_beat + (cur_meas<<4)] -= 12; refresh(); } } else if (command == K_ENTER) { value = getLength(); // if part of held note, have to modify all notes held if (value > 1) { pitch = pch[cur_track][cur_beat + (cur_meas<<4)]; for(i=1;i<value;i++) { pch[cur_track][((cur_beat + (cur_meas<<4)) + i) & ((NMEAS<<4) - 1)] = (pitch | 0x80); } } mode = M_NORM; refresh(); } else if (command == K_CANCEL) { pch[cur_track][cur_beat + (cur_meas<<4)] = saved_val; //restore previous value mode = M_NORM; refresh(); } break; case M_VEL: //parse command as numerical keypad with enter, cancel if (command <= 2) value = command + 1; else if ((command >= 4) && (command <= 6)) value = command; else if ((command >= 8) && (command <= 10)) value = command-1; else if (command == 13) value = 0; else if (command == K_ENTER) { value = getLength(); // if part of held note, have to modify all notes held if (value > 1) { velocity = vel[cur_track][cur_beat + (cur_meas<<4)]; for(i=1;i<value;i++) { vel[cur_track][((cur_beat + (cur_meas<<4)) + i) & ((NMEAS<<4) - 1)] = velocity; } } mode = M_NORM; refresh(); } else if (command == K_CANCEL) { vel[cur_track][cur_beat + (cur_meas<<4)] = saved_val; //restore previous value mode = M_NORM; refresh(); } //if value entered is a digit if (value <= 9) { field[field_count] = value; //format fieldstr to display the digits as they are entered by the user if (field_count == 0) sprintf(fieldstr,"%-d__",field[0]); else if (field_count == 1) sprintf(fieldstr,"%-d%-d_",field[0],field[1]); else if (field_count == 2) sprintf(fieldstr,"%-d%-d%-d",field[0],field[1],field[2]); field_count++; refresh(); //if 3 digits have been entered, calculate the numerical value, and perform a range check if (field_count == 3) { value = (char)((100 * field[0]) + (10 * field[1]) + field[2]); if ((field[0] > 1) || ((field[0] == 1) && (field[1] > 2)) || ((field[0] == 1) && (field[1] == 2) && (field[2] > 7))) vel[cur_track][cur_beat + (cur_meas<<4)] = 127; //out of range, use maximum else vel[cur_track][cur_beat + (cur_meas<<4)] = value; //in range, use the value //refresh the display, and reinitialize fieldstr so that the user can input //another value if desired field_count = 0; refresh(); sprintf(fieldstr,"___"); } } break; case M_LENGTH: //parse command as numerical keypad with enter, cancel if (command <= 2) value = command + 1; else if ((command >= 4) && (command <= 6)) value = command; else if ((command >= 8) && (command <= 10)) value = command-1; else if (command == 13) value = 0; else if (command == K_ENTER) { value = getLength(); pitch = pch[cur_track][cur_beat + (cur_meas<<4)]; velocity = vel[cur_track][cur_beat + (cur_meas<<4)]; //copies current values of pitch and velocity to all held notes for(i=1;i<saved_val;i++) { pch[cur_track][((cur_beat + (cur_meas<<4)) + i) & ((NMEAS<<4) - 1)] = (pitch | 0x80); vel[cur_track][((cur_beat + (cur_meas<<4)) + i) & ((NMEAS<<4) - 1)] = velocity; } //if length input is smaller than previous length, must remove old values if (saved_val < value) { for(i=saved_val;i<value;i++) { pch[cur_track][((cur_beat + (cur_meas<<4)) + i) & ((NMEAS<<4) - 1)] = (pitch & 0x7F); vel[cur_track][((cur_beat + (cur_meas<<4)) + i) & ((NMEAS<<4) - 1)] = 0; } } mode = M_NORM; refresh(); } else if (command == K_CANCEL) { mode = M_NORM; refresh(); } //format for display, same as in velocity edit mode, except with 2 digits instead of 3 if (value <= 9) { field[field_count] = value; if (field_count == 0) sprintf(fieldstr,"%-d_",field[0]); else if (field_count == 1) sprintf(fieldstr,"%-d%-d",field[0],field[1]); field_count++; refresh(); if (field_count == 2) { value = (char)((10 * field[0]) + field[1]); if ((field[0] > 3) || ((field[0] == 3) && (field[1] > 2))) saved_val = 32; else saved_val = value; field_count = 0; refresh(); sprintf(fieldstr,"__"); } } break; case M_TEMPO: //parse command as numerical keypad with enter, cancel if (command <= 2) value = command + 1; else if ((command >= 4) && (command <= 6)) value = command; else if ((command >= 8) && (command <= 10)) value = command-1; else if (command == 13) value = 0; else if (command == K_ENTER) { //calculate the period of a sixteenth note from the tempo entered in bpm flt_temp = (float)tempo; flt_temp = 60 / flt_temp; flt_temp = flt_temp * 15625; OCR1A = (int)flt_temp; mode = M_NORM; refresh(); } else if (command == K_CANCEL) { tempo = saved_val; //restore previous value mode = M_NORM; refresh(); } //format for display, same as in velocity edit mode if (value <= 9) { field[field_count] = value; if (field_count == 0) sprintf(fieldstr,"%-d__",field[0]); else if (field_count == 1) sprintf(fieldstr,"%-d%-d_",field[0],field[1]); else if (field_count == 2) sprintf(fieldstr,"%-d%-d%-d",field[0],field[1],field[2]); field_count++; refresh(); if (field_count == 3) { value = (char)((100 * field[0]) + (10 * field[1]) + field[2]); if ((field[0] > 2) || ((field[0] == 2) && (field[1] > 4)) || ((field[0] == 2) && (field[1] == 4) && (field[2] > 0))) tempo = 240; else if (value < 60) tempo = 60; else tempo = value; field_count = 0; refresh(); sprintf(fieldstr,"___"); } } break; }// end switch(mode) command = 0xff; //command is done being executed, don't want to execute again } } void main(void) { unsigned char i; initialize(); while(1) { //every 30ms if (stateFlag == 1) { stateFlag = 0; stateMachine(); //enter keypad scanning state machine doCommand(); //perform selected command //note has been triggered, decrement counter if (trig_timer > 0) { trig_timer--; trigger = 1; } } //if trig_timer has been modified if (trigger == 1) { trigger = 0; //if timer was just started, play note if (trig_timer == 9) { putchar(0x90); putchar(pch[cur_track][cur_beat + (cur_meas<<4)]); putchar(vel[cur_track][cur_beat + (cur_meas<<4)]); //else if count reaches 0 (about 270ms), stop note } else if (trig_timer == 0) { putchar(0x90); putchar(pch[cur_track][cur_beat + (cur_meas<<4)]); putchar(0x00); } } //play_flag is 1 only when the sequence was just started if (play_flag == 0) { if (prepare == 1) { prepare = 0; prepBuffer(); } } else { // need to buffer values before timer1 starts for(i=0;i<NTRK;i++) beat[i] = 0; prepare = 0; prepBuffer(); play_flag = 0; //start timer1, set to interrupt right away TCNT1 = OCR1A-1; TCCR1B = 0x0b; } } } //scans the keypad and returns the button currently pressed by the user, or 0xff if nothing is pressed char getButton(void) { char key,butnum; DDRC = 0x0f; PORTC = 0xf0; delay_us(5); key = PINC; DDRC = 0xf0; PORTC = 0x0f; delay_us(5); key = key | PINC; if (key != 0xff) { for(butnum=0;butnum<16;butnum++) { if (keytbl[butnum]==key) break; } if (butnum==16) butnum=-1; } else butnum = -1; return butnum; } //debounces the keypad, sets command variable to the key pressed void stateMachine(void) { char key; key = getButton(); switch(state) { case 0: if (key != 0xff) { key2 = key; state = 1; } break; case 1: if (key==key2) { state = 2; command = key; } else state = 0; break; case 2: if (key!=key2) state = 3; break; case 3: if (key==key2) state = 2; else state = 0; break; } } //initializes MCU, and enters preset song if preset button detected void initialize(void) { int i,j; char select; DDRD = 0xff; //serial port UBRR = 7; //31.25kBaud UCR = 0x08; //UART transmit enable TCCR1B = 0x00; //timer1 stopped, to start use 0x0b TIMSK = 0x40; //enable timer1 compare match A interrupt TCNT1 = 0x00; //reset timer1 OCR1A = 7812; // 120 bpm TCNT0 = 139; //reload timer0 TCCR0 = 5; //scale timer0 by 1024 TIMSK = TIMSK | 2; //enable timer0 overflow interrupt //initialize arrays, variables for(i=0;i<NTRK;i++) { for(j=0;j<(NMEAS<<4);j++) { pch[i][j] = 0x00; vel[i][j] = 0x00; } } tempo = 120; cur_track = 0; cur_meas = 0; cur_beat = 0; loop[0] = 31; loop[1] = 31; loop[2] = 31; loop[3] = 31; prepare = 0; state = 0; stateFlag = 0; trigger = 0; mode = M_NORM; //initialize LCD lcd_init(20); lcd_clear(); refresh(); //if preset button is pushed, load the preset sequence select = getButton(); if (select == K_PRESET) { //C#5 = bassdrum pch[0][0] = 61; vel[0][0] = 90; pch[0][4] = 61; vel[0][4] = 90; pch[0][8] = 61; vel[0][8] = 90; pch[0][12] = 61; vel[0][12] = 90; pch[0][16] = 61; vel[0][16] = 90; pch[0][20] = 61; vel[0][20] = 90; pch[0][24] = 61; vel[0][24] = 90; pch[0][28] = 61; vel[0][28] = 90; pch[0][30] = 61; vel[0][30] = 90; //D5 = snaredrum pch[1][4] = 62; vel[1][4] = 80; pch[1][12 Build your own community today with the largest message board hosting company. |