/* MIDI Sequencer EE476 Final Project Nicholas Kisler */ #include <90s8515.h> #include #include #include #asm .equ __lcd_port=0x1b #endasm #include //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 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>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 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= 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 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 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