1 /*
2  * Komposter
3  *
4  * Copyright (c) 2010 Noora Halme et al. (see AUTHORS)
5  *
6  * This code is licensed under the GNU General Public
7  * License version 2. See LICENSE for full text.
8  *
9  * Pattern editor page
10  *
11  */
12 
13 #include "pattern.h"
14 
15 // button indexes
16 #define B_PREV 0
17 #define B_NEXT 1
18 #define B_SHORTER 2
19 #define B_LONGER 3
20 #define B_OCTUP 4
21 #define B_OCTDN 5
22 #define B_PREVSYN 6
23 #define B_SYNNAME 7
24 #define B_NEXTSYN 8
25 #define B_PATTPLAY 9
26 #define B_PATTLOAD 10
27 #define B_PATTSAVE 11
28 
29 #define B_PATTCLEAR 12
30 
31 #define B_COPY 13
32 #define B_PASTE 14
33 
34 #define KB_WIDTH 40
35 
36 #define PIANOROLL_Y 528.5
37 #define PIANOROLL_X KB_WIDTH+5.5
38 
39 #define PIANOROLL_HEIGHT 336
40 
41 #define PIANOROLL_CELLWIDTH 11
42 
43 #define PIANOROLL_CELLHEIGHT 7
44 #define PIANOROLL_BEAT (PIANOROLL_CELL*4)
45 
46 #define PIANOROLL_OCTAVES 6
47 
48 
49 // from modules.c
50 extern float pitch[MAX_SYNTH];
51 extern int accent[MAX_SYNTH];
52 extern int gate[MAX_SYNTH];
53 
54 extern int audiomode; // from audio.c
55 extern unsigned long playpos;
56 extern unsigned int audiomode_flags;
57 
58 // from sequencer.c
59 extern int bpm;
60 extern int beats_per_measure;
61 extern int beatdiv;
62 
63 extern int csynth; // from synthesizer.c
64 extern char synthname[MAX_SYNTH][128]; // from synthesizer.c
65 extern char patchname[MAX_SYNTH][MAX_PATCHES][128]; // from patch.c
66 
67 extern synthmodule mod[MAX_SYNTH][MAX_MODULES]; // ditto
68 extern int signalfifo[MAX_SYNTH][MAX_MODULES]; // module execution stack
69 extern int cpatch[MAX_SYNTH]; // selected patch for each synth
70 
71 
72 // from patch.c
73 extern char pianokeys[29];
74 extern int kpkeydown;
75 
76 
77 // black or white key
78 const unsigned char keycolor[12]={1,0,1,0,1,1,0,1,0,1,0,1};
79 
80 
81 u32 pattdata[MAX_PATTERN][MAX_PATTLENGTH];
82 u32 pattlen[MAX_PATTERN];
83 
84 u32 patt_clipboard[MAX_PATTLENGTH];
85 u32 patt_clipboard_len;
86 
87 int cpatt, coct;
88 
89 int patt_ui[15];
90 int piano_hover, piano_note, piano_drag, piano_dragto;
91 int piano_start;
92 int slide_hover;
93 float slide_drag_xofs;
94 int slide_drag, slide_drag_start;
95 int patt_cpatch;
96 
97 int piano_porta_drag, piano_porta_drag_len, piano_porta_drag_from;
98 
99 // initialize pattern data to defaults
pattern_init()100 void pattern_init()
101 {
102   int i,j;
103 
104   cpatt=0;
105   coct=3;
106   for(i=0;i<MAX_PATTERN;i++) {
107     pattlen[i]=4;
108     for(j=0;j<MAX_PATTLENGTH;j++) pattdata[i][j]=0;
109   }
110   piano_note=-1; piano_hover=-1; piano_drag=-1; piano_dragto=-1;
111   piano_start=0; slide_hover=0;
112   patt_cpatch=0;
113   piano_porta_drag=-1; piano_porta_drag_len=0;
114   for(i=0;i<13;i++) patt_ui[i]=0;
115   patt_clipboard_len=0;
116 }
117 
118 
pattern_toggleplayback()119 void pattern_toggleplayback()
120 {
121   int patt_playing;
122 
123   patt_playing=((audiomode==AUDIOMODE_PATTERNPLAY)&1)^1;
124   patt_ui[B_PATTPLAY]&=1;
125   patt_ui[B_PATTPLAY]|=(patt_playing<<1);
126   playpos=0;
127   gate[0]=0;
128   if (patt_playing) {
129     audiomode=AUDIOMODE_PATTERNPLAY; audiomode_flags|=1;
130   } else {
131     audiomode=AUDIOMODE_COMPOSING;
132   }
133 }
134 
135 
136 
137 // returns pattern offset the cursor is on top of or -1 if the cursor is currently
138 // off the piano roll. when a position is returned, the int pointed by note is set to
139 // the midi note number under the cursor.
pattern_cursorpos(int x,int y,int * note)140 int pattern_cursorpos(int x, int y, int *note)
141 {
142   int cx,cy;
143   float lineheight;
144 
145   if (((coct+PIANOROLL_OCTAVES)*12)<MAX_NOTE) {
146     lineheight=PIANOROLL_OCTAVES*12;
147   } else {
148     lineheight=PIANOROLL_OCTAVES*12 - (((coct+PIANOROLL_OCTAVES)*12)-MAX_NOTE);
149   }
150 
151   if (
152       x >  PIANOROLL_X  &&
153       x < (PIANOROLL_X + (pattlen[cpatt]*(beats_per_measure*beatdiv))*PIANOROLL_CELLWIDTH + 1) &&
154       y < PIANOROLL_Y  + 3 &&
155       y > (PIANOROLL_Y + 1 - lineheight*PIANOROLL_CELLHEIGHT)
156      )
157   {
158     cx=(int)((x-PIANOROLL_X)/PIANOROLL_CELLWIDTH)-1;
159     cy=abs((int)((y-PIANOROLL_Y-2)/PIANOROLL_CELLHEIGHT));
160     if (note) *note=coct*12+cy;
161     return cx;
162   }
163   return -1;
164 }
165 
166 
pattern_mouse_hover(int x,int y)167 void pattern_mouse_hover(int x, int y)
168 {
169 
170   // test buttons
171   patt_ui[B_PREV]=hovertest_box(x, y, 14,  DS_HEIGHT-14, 16, 16);
172   patt_ui[B_NEXT]=hovertest_box(x, y, 64,  DS_HEIGHT-14, 16, 16);
173   patt_ui[B_SHORTER]=hovertest_box(x, y, 98,  DS_HEIGHT-14, 16, 16);
174   patt_ui[B_LONGER]=hovertest_box(x, y, 180,  DS_HEIGHT-14, 16, 16);
175   patt_ui[B_OCTDN]=hovertest_box(x, y, 12, PIANOROLL_Y+16, 16, 16);
176   patt_ui[B_OCTUP]=hovertest_box(x, y, 32, PIANOROLL_Y+16, 16, 16);
177   patt_ui[B_PREVSYN]=hovertest_box(x, y, 214, DS_HEIGHT-14, 16, 16);
178   patt_ui[B_NEXTSYN]=hovertest_box(x, y, 430, DS_HEIGHT-14, 16, 16);
179 
180   patt_ui[B_PATTCLEAR]=hovertest_box(x, y, 555, DS_HEIGHT-14, 16, 16) | (patt_ui[B_PATTCLEAR]&8);
181 
182   patt_ui[B_COPY]=hovertest_box(x,y,622, DS_HEIGHT-14, 16, 16);
183   patt_ui[B_PASTE]=hovertest_box(x,y,644, DS_HEIGHT-14, 16, 16);
184   if (patt_clipboard_len==0) patt_ui[B_PASTE]=0;
185 
186   patt_ui[B_PATTPLAY]=hovertest_box(x, y, 482, DS_HEIGHT-14, 16, 42);
187   if (audiomode==AUDIOMODE_PATTERNPLAY) patt_ui[B_PATTPLAY]|=2;
188 
189   // hovering above piano roll?
190   piano_note=-1;
191   piano_hover=pattern_cursorpos(x,y,&piano_note);
192 
193   // hovering above slider?
194   slide_hover=hovertest_hslider(x,y,PIANOROLL_X, PIANOROLL_Y+12, (DS_WIDTH-(PIANOROLL_X+6)), 12,
195    piano_start, (DS_WIDTH-(PIANOROLL_X+4))/PIANOROLL_CELLWIDTH, pattlen[cpatt]*(beats_per_measure*beatdiv));
196 
197   // any ui-elements active?
198   //for(m=0;m<7;m++) if (patt_ui[m]&1) return;
199 }
200 
201 
pattern_mouse_drag(int x,int y)202 void pattern_mouse_drag(int x, int y)
203 {
204   int m,n,note,i;
205   float f, cos, cip, sbw, slw;
206 
207   // dragging a new note, paint all cells from drag point to current cell
208   if (piano_drag >= 0) {
209     m=pattern_cursorpos(x, y, &note); // disregard note
210     if (m>=piano_drag) piano_dragto=m;
211     return;
212   }
213 
214   // dragging the piano roll scrollbar
215   if (slide_drag>0) {
216     sbw=DS_WIDTH-(PIANOROLL_X+6); // scrollbar width
217     cos=(DS_WIDTH-(PIANOROLL_X))/PIANOROLL_CELLWIDTH; // cells on screen
218     cip=pattlen[cpatt]*(beats_per_measure*beatdiv); // cells in pattern
219     slw=sbw*(cos/cip);
220     f=((x-slide_drag_xofs)/(sbw-slw))*(cip-cos);
221     piano_start=slide_drag_start+f;
222     if (piano_start>(1+cip-cos)) piano_start=(1+cip-cos);
223     if (piano_start<0) piano_start=0;
224     return;
225   }
226 
227   if (piano_porta_drag >= 0) {
228     n=pattdata[cpatt][piano_start+piano_porta_drag]; // current note being dragged
229     m=pattern_cursorpos(x, y, &note);
230     if (note != n) {
231       // replace the note on the pattern data
232       for(i=0;i<piano_porta_drag_len;i++) {
233         pattdata[cpatt][piano_start+piano_porta_drag+i]&=0xff80;
234         pattdata[cpatt][piano_start+piano_porta_drag+i]|=note&0xff;
235       }
236       piano_note=note&0xff;
237       //audio_trignote(0, piano_note); // audio feedback
238     }
239   }
240 }
241 
242 
pattern_mouse_click(int button,int state,int x,int y)243 void pattern_mouse_click(int button, int state, int x, int y)
244 {
245   int i,m,j,l;
246   char tmps[256];
247 
248   if (button==GLUT_LEFT_BUTTON) {
249     if (state==GLUT_DOWN) {
250 
251       // test first if piano roll is clicked with modifiers
252       m=glutGetModifiers();
253       if (m==GLUT_ACTIVE_SHIFT) {
254         if (piano_hover>=0 && piano_note==(pattdata[cpatt][piano_start+piano_hover]&0xff)) {
255           // wipe the note from begin to end
256           i=piano_start+piano_hover;
257           while(pattdata[cpatt][i]&NOTE_LEGATO) i--;
258           do { pattdata[cpatt][i++]=0;
259           } while(pattdata[cpatt][i]&NOTE_LEGATO);
260           return;
261         }
262       }
263 
264       // clear the pattern
265       if (patt_ui[B_PATTCLEAR]&1) {
266         if (patt_ui[B_PATTCLEAR]&8) {
267           for(i=0;i<MAX_PATTLENGTH;i++) pattdata[cpatt][i]=0;
268           sprintf(tmps, "Pattern %02d cleared", cpatt);
269           console_post(tmps);
270           patt_ui[B_PATTCLEAR]&=0xff-8;
271         } else {
272           console_post("Click again to clear pattern");
273           patt_ui[B_PATTCLEAR]|=8;
274         }
275         return;
276       } else {
277         patt_ui[B_PATTCLEAR]&=0xff-8;
278       }
279 
280       // click on the ui buttons
281       if (patt_ui[B_PREV]) { if (cpatt>0) cpatt--; return; }
282       if (patt_ui[B_NEXT]) { if (cpatt<MAX_PATTERN) cpatt++; return; }
283 
284       if (patt_ui[B_SHORTER]) {
285         if (pattlen[cpatt]>1) {
286           // clear the pattern data no longer visible if shift down
287           m=glutGetModifiers();
288           if (m==GLUT_ACTIVE_SHIFT) {
289              for(i=((pattlen[cpatt]*(beats_per_measure*beatdiv))/2); i<(pattlen[cpatt]*(beats_per_measure*beatdiv)); i++) pattdata[cpatt][i]=0;
290           }
291 
292           pattlen[cpatt]/=2;
293 
294           while (piano_start >
295             ( 1+pattlen[cpatt]*(beats_per_measure*beatdiv) - ( (DS_WIDTH-(PIANOROLL_X))/PIANOROLL_CELLWIDTH ) ) ) piano_start--;
296           if (piano_start<0) piano_start=0;
297         }
298         return;
299       }
300 
301       if (patt_ui[B_LONGER]) {
302         if (pattlen[cpatt]<16) {
303           pattlen[cpatt]*=2;
304 
305           // duplicate first half of pattern to second half if shift pressed
306           m=glutGetModifiers();
307           if (m==GLUT_ACTIVE_SHIFT) {
308             for(i=0; i<((pattlen[cpatt]*(beats_per_measure*beatdiv))/2); i++) pattdata[cpatt][i+((pattlen[cpatt]*(beats_per_measure*beatdiv))/2)]=pattdata[cpatt][i];
309           }
310         }
311         return;
312       }
313 
314       if (patt_ui[B_OCTDN]) { if (coct>0) coct--; return; }
315       if (patt_ui[B_OCTUP]) { if (coct<MAX_OCTAVE) coct++; return; }
316 
317       if (patt_ui[B_PREVSYN]) { if (csynth[cpatch]>0) csynth[cpatch]--;
318         audio_loadpatch(0, csynth, cpatch[csynth]);
319         return; }
320       if (patt_ui[B_NEXTSYN]) { if (csynth[cpatch]<(MAX_PATCHES-1)) csynth[cpatch]++;
321         audio_loadpatch(0, csynth, cpatch[csynth]);
322         return; }
323 
324       if (patt_ui[B_PATTPLAY]&1) pattern_toggleplayback();
325 
326       if (patt_ui[B_COPY]) {
327         // copy pattern data to clipboard
328         for(i=0; i<pattlen[cpatt]*(beats_per_measure*beatdiv); i++) patt_clipboard[i]=pattdata[cpatt][i];
329         patt_clipboard_len=pattlen[cpatt]*(beats_per_measure*beatdiv);
330         console_post("Current pattern copied to clipboard");
331       }
332 
333       if (patt_ui[B_PASTE] && patt_clipboard_len>0) {
334         for(i=0; i<patt_clipboard_len; i++) pattdata[cpatt][i]=patt_clipboard[i];
335       }
336 
337       // click on the piano roll?
338       if (piano_hover>=0) {
339 
340         if (piano_note==(pattdata[cpatt][piano_start+piano_hover]&0xff)) {
341           // clicked on an existing note, start drag up/down
342           j=piano_hover;
343           if (pattdata[cpatt][piano_start+j]&NOTE_LEGATO) {
344             while (pattdata[cpatt][piano_start+j]&NOTE_LEGATO) j--;
345           }
346           for(l=1; pattdata[cpatt][piano_start+j+l]&NOTE_LEGATO; l++);
347           piano_porta_drag=j;
348           piano_porta_drag_len=l;
349           piano_porta_drag_from=pattdata[cpatt][piano_start+j]&0xff;
350         } else {
351           // create a new note - set the 1/16th note on the pattern
352           pattdata[cpatt][piano_start+piano_hover]=piano_note;
353           piano_drag=piano_hover; //drag note stating from this cell
354           piano_dragto=piano_hover;
355 
356           // trig the note to get an audible feedback
357           audio_trignote(0, piano_note);
358         }
359       }
360 
361       // click on the slider?
362       if (slide_hover) {
363         slide_drag=1;
364         slide_drag_xofs=x;
365         slide_drag_start=piano_start;
366       }
367     }
368     if (state==GLUT_UP) {
369       if (slide_drag) { slide_drag=0; return; }
370       if (piano_drag>=0) {
371         // released the drag, draw the notes with legato and reset drag
372         for(m=piano_drag;m<=piano_dragto;m++) pattdata[cpatt][piano_start+m]=pattdata[cpatt][piano_start+piano_drag]|NOTE_LEGATO;
373         pattdata[cpatt][piano_start+piano_drag]&=0xff; // legato off from the first note
374         pattdata[cpatt][piano_start+piano_dragto+1]&=0xff; // legato off from the note which follows
375         piano_drag=-1;
376         piano_dragto=-1;
377 
378         gate[0]=0; // note off
379       }
380       if (piano_porta_drag>=0) {
381         piano_porta_drag=-1;
382         piano_porta_drag_len=0;
383 
384         gate[0]=0; // note off
385       }
386     }
387   }
388 
389 
390   if (button==GLUT_RIGHT_BUTTON) {
391     if (state==GLUT_DOWN) {
392 
393       if (piano_hover>=0) {
394         // right click to set accent
395         if (pattdata[cpatt][piano_start+piano_hover]) { // got a note here
396           if (pattdata[cpatt][piano_start+piano_hover]&NOTE_LEGATO) {
397             //printf("legato here\n"); // do nothing
398           } else {
399             // first note, so mark it with an accent
400             pattdata[cpatt][piano_start+piano_hover]^=NOTE_ACCENT;
401           }
402         }
403       }
404 
405 
406     }
407   }
408 }
409 
410 
411 // keyboard callback
pattern_keyboard(unsigned char key,int x,int y)412 void pattern_keyboard(unsigned char key, int x, int y)
413 {
414   int i,pkey;
415 
416   switch (key) {
417     case ' ': pattern_toggleplayback(); return; break;
418     case '-':
419       if (pattlen[cpatt]>1) pattlen[cpatt]/=2;
420       while (piano_start >
421         ( 1+pattlen[cpatt]*(beats_per_measure*beatdiv) - ( (DS_WIDTH-(PIANOROLL_X))/PIANOROLL_CELLWIDTH ) ) ) piano_start--;
422       if (piano_start<0) piano_start=0;
423       return;
424       break;
425     case '+':
426       if (pattlen[cpatt]<16) pattlen[cpatt]*=2;
427       return;
428       break;
429     case ',':
430       if (csynth[cpatch]>0) csynth[cpatch]--;
431       audio_loadpatch(0, csynth, cpatch[csynth]);
432       return;
433       break;
434     case '.':
435       if (csynth[cpatch]<(MAX_PATCHES-1)) csynth[cpatch]++;
436       audio_loadpatch(0, csynth, cpatch[csynth]);
437       return;
438       break;
439   }
440 
441   // keyjazzing! w00t!
442   for (i=0;i<29;i++) {
443     if (pianokeys[i]==key) {
444       // trig note. use same base octave as pattern editor
445       pkey=coct*12 + i;
446       kpkeydown=pkey;
447       audio_trignote(0, pkey);
448     }
449   }
450 }
451 
452 
pattern_specialkey(unsigned char key,int x,int y)453 void pattern_specialkey(unsigned char key, int x, int y)
454 {
455   switch(key)
456   {
457     case GLUT_KEY_LEFT:
458       if (cpatt>0) cpatt--;
459       break;
460 
461     case GLUT_KEY_RIGHT:
462       if (cpatt<MAX_PATTERN) cpatt++;
463       break;
464 
465     case GLUT_KEY_DOWN:
466       if (coct>0) coct--;
467       break;
468 
469     case GLUT_KEY_UP:
470       if (coct<(9-PIANOROLL_OCTAVES)) coct++;
471       break;
472 
473     case GLUT_KEY_PAGE_DOWN:
474       break;
475 
476     case GLUT_KEY_PAGE_UP:
477       break;
478   }
479 }
480 
481 
482 
483 
484 
pattern_keyboardup(unsigned char key,int x,int y)485 void pattern_keyboardup(unsigned char key, int x, int y)
486 {
487   int i;
488 
489   for (i=0;i<29;i++) {
490     if (pianokeys[i]==key) {
491       // if this key is still down, drop gate and trig
492       if (kpkeydown==(i+coct*12)) {
493         gate[0]=0;
494         kpkeydown=-1;
495       }
496     }
497   }
498 }
499 
500 
501 
502 
503 
pattern_draw(void)504 void pattern_draw(void)
505 {
506   int i,j,n,l;
507   char tmps[256];
508   long ticks;
509   int rkdown;
510   float lineheight;
511 
512   // draw keyboard, highlight a note if cursor is on piano roll
513   rkdown=-1;
514   if (piano_drag>=0) rkdown=piano_drag;
515   if (kpkeydown>=0) rkdown=kpkeydown;
516   for(i=0;i<PIANOROLL_OCTAVES;i++)
517     if ((coct+i)<10)draw_kboct(round(PIANOROLL_Y-(12*PIANOROLL_CELLHEIGHT*i)), 40, 12, coct+i, piano_note, rkdown);
518 
519   // backgrounds and divider lines for note grid
520   for(j=0;j<(PIANOROLL_OCTAVES*12+1);j++) {
521     if (coct*12+j > MAX_NOTE) continue;
522 
523      if (keycolor[j%12]) {
524        glColor4ub(0x20, 0x20, 0x20, 0xff);
525      } else {
526        glColor4ub(0x00, 0x00, 0x00, 0xff);
527      }
528      glBegin(GL_QUADS);
529        glVertex2f(PIANOROLL_X,                                                     PIANOROLL_Y-(j*PIANOROLL_CELLHEIGHT));
530        glVertex2f(PIANOROLL_X+(pattlen[cpatt]*(beats_per_measure*beatdiv)-piano_start)*PIANOROLL_CELLWIDTH, PIANOROLL_Y-(j*PIANOROLL_CELLHEIGHT));
531        glVertex2f(PIANOROLL_X+(pattlen[cpatt]*(beats_per_measure*beatdiv)-piano_start)*PIANOROLL_CELLWIDTH, PIANOROLL_Y-((j+1)*PIANOROLL_CELLHEIGHT));
532        glVertex2f(PIANOROLL_X,                                                     PIANOROLL_Y-((j+1)*PIANOROLL_CELLHEIGHT));
533      glEnd();
534 
535     glColor4f(0.3, 0.3, 0.3, 0.8);
536     if ((j%12)==0)  glColor4f(0.5, 0.5, 0.5, 0.8);
537     glEnable(GL_LINE_STIPPLE);
538     glLineStipple(1, 0xaaaa);
539     glBegin(GL_LINES);
540     glVertex2f(PIANOROLL_X,     PIANOROLL_Y-(j*PIANOROLL_CELLHEIGHT));
541     glVertex2f(PIANOROLL_X+(pattlen[cpatt]*(beats_per_measure*beatdiv)-piano_start)*PIANOROLL_CELLWIDTH, PIANOROLL_Y-(j*PIANOROLL_CELLHEIGHT));
542     glEnd();
543     glDisable(GL_LINE_STIPPLE);
544   }
545 
546   // draw markers for beats and measures
547   if (((coct+PIANOROLL_OCTAVES)*12)<MAX_NOTE) {
548     lineheight=PIANOROLL_OCTAVES*12;
549   } else {
550     lineheight=PIANOROLL_OCTAVES*12 - (((coct+PIANOROLL_OCTAVES)*12)-MAX_NOTE);
551   }
552   lineheight*=PIANOROLL_CELLHEIGHT;
553   for(i=0,j=piano_start;j<(pattlen[cpatt]*(beats_per_measure*beatdiv)+1);i++,j++) {
554     glColor4f(0.2, 0.2, 0.2, 0.8);
555     if ((j%beatdiv)==0) {
556       // beat
557       glColor4f(0.5, 0.5, 0.5, 0.8);
558     }
559     int b=beats_per_measure*beatdiv;
560     if ((j%b)==0 && j<(pattlen[cpatt]*b)) {
561       // measure
562       sprintf(tmps, "%1d", (j/(beats_per_measure*beatdiv)));
563       render_text(tmps, 2.5+PIANOROLL_X+(i*PIANOROLL_CELLWIDTH), round(PIANOROLL_Y+PIANOROLL_CELLHEIGHT), 2, 0xffa0a0a0, 0);
564       glColor4f(0.7, 0.7, 0.7, 0.8);
565     }
566     glEnable(GL_LINE_STIPPLE);
567     glLineStipple(1, 0xaaaa);
568     glBegin(GL_LINES);
569     glVertex2f(PIANOROLL_X+i*PIANOROLL_CELLWIDTH, round(PIANOROLL_Y+PIANOROLL_CELLHEIGHT));
570     glVertex2f(PIANOROLL_X+i*PIANOROLL_CELLWIDTH, round(PIANOROLL_Y-lineheight)); //(PIANOROLL_OCTAVES*12)*PIANOROLL_CELLHEIGHT)));
571     glEnd();
572     glDisable(GL_LINE_STIPPLE);
573   }
574 
575   // draw the playing position if pattern is playing
576   if (audiomode==AUDIOMODE_PATTERNPLAY) {
577     ticks=playpos / (OUTPUTFREQ/(bpm*256/60)); // calc tick from sample index
578     if ( (ticks>>6)>=piano_start && (ticks>>6)<(pattlen[cpatt]*(beats_per_measure*beatdiv)) ) // TODO: test if pos > pianostart+rollwidth
579     {
580       // within bounds
581       i=(ticks>>6)-piano_start;
582       glColor4f(1.0, 1.0, 1.0, 1.0);
583       glBegin(GL_LINES);
584       glVertex2f(PIANOROLL_X+i*PIANOROLL_CELLWIDTH, round(PIANOROLL_Y+PIANOROLL_CELLHEIGHT));
585       glVertex2f(PIANOROLL_X+i*PIANOROLL_CELLWIDTH, round(PIANOROLL_Y-((PIANOROLL_OCTAVES*12)*PIANOROLL_CELLHEIGHT)));
586       glEnd();
587     }
588   }
589 
590   // draw the notes to the piano roll
591   for(i=0,j=piano_start;j<(pattlen[cpatt]*(beats_per_measure*beatdiv));i++,j++) {
592     n=pattdata[cpatt][j] & 0xff; // take only the low byte
593     l=(pattdata[cpatt][j]&NOTE_LEGATO) ? 0 : 1; // if legato, extend note for a pixel left
594     if (n>0) { // todo: fill with -1 instead as 0 is a valid midi note
595       if ( n>=(coct*12) && n<((coct+PIANOROLL_OCTAVES)*12)) {
596         // on the visible three octaves - plot it
597         glColor4ub(0xb0, 0x55, 0x00, 0xff);
598         glBegin(GL_QUADS);
599           glVertex2f(l+PIANOROLL_X+i*PIANOROLL_CELLWIDTH, PIANOROLL_Y-1-(n-coct*12)*PIANOROLL_CELLHEIGHT);
600           glVertex2f(PIANOROLL_X+(i+1)*PIANOROLL_CELLWIDTH, PIANOROLL_Y-1-(n-coct*12)*PIANOROLL_CELLHEIGHT);
601           glVertex2f(PIANOROLL_X+(i+1)*PIANOROLL_CELLWIDTH, PIANOROLL_Y-(n-coct*12+1)*PIANOROLL_CELLHEIGHT);
602           glVertex2f(l+PIANOROLL_X+i*PIANOROLL_CELLWIDTH, PIANOROLL_Y-(n-coct*12+1)*PIANOROLL_CELLHEIGHT);
603         glEnd();
604         if (pattdata[cpatt][j]&NOTE_ACCENT) {
605           // draw the accent mark
606           render_text(">",
607             l+PIANOROLL_X+i*PIANOROLL_CELLWIDTH+1,
608             PIANOROLL_Y-2-(n-coct*12+1)*PIANOROLL_CELLHEIGHT,
609             2, 0x000000ff, 0);
610           render_text(">",
611             l+PIANOROLL_X+i*PIANOROLL_CELLWIDTH,
612             PIANOROLL_Y-2-(n-coct*12+1)*PIANOROLL_CELLHEIGHT,
613             2, 0xffffffff, 0);
614         }
615       }
616     }
617   }
618 
619   // draw the note highlighted by cursor
620   if (piano_hover >= 0) {
621     n=pattdata[cpatt][piano_start+piano_hover]&0xff;
622     if (n>0 && piano_note==n) {
623       j=piano_hover;
624       if (pattdata[cpatt][piano_start+j]&NOTE_LEGATO) {
625         // search backwards to find start of note
626         while (pattdata[cpatt][piano_start+j]&NOTE_LEGATO) j--;
627       }
628       for(l=1; pattdata[cpatt][piano_start+j+l]&NOTE_LEGATO; l++);
629 
630       glColor4f(0.8, 0.8, 0.8, 0.8);
631       glEnable(GL_LINE_STIPPLE);
632       glLineStipple(1, 0x3333);
633       glBegin(GL_LINE_LOOP);
634       glVertex2f(1+PIANOROLL_X+j*PIANOROLL_CELLWIDTH, PIANOROLL_Y-1-(n-coct*12)*PIANOROLL_CELLHEIGHT);
635       glVertex2f(PIANOROLL_X+(j+l)*PIANOROLL_CELLWIDTH, PIANOROLL_Y-1-(n-coct*12)*PIANOROLL_CELLHEIGHT);
636       glVertex2f(PIANOROLL_X+(j+l)*PIANOROLL_CELLWIDTH, PIANOROLL_Y-(n-coct*12+1)*PIANOROLL_CELLHEIGHT);
637       glVertex2f(1+PIANOROLL_X+j*PIANOROLL_CELLWIDTH, PIANOROLL_Y-(n-coct*12+1)*PIANOROLL_CELLHEIGHT);
638       glEnd();
639       glDisable(GL_LINE_STIPPLE);
640     }
641   }
642 
643   // drag an existing note up or down
644   if (piano_porta_drag >= 0) {
645     n=pattdata[cpatt][piano_start+piano_porta_drag];
646     glColor4ub(0xc0, 0xc0, 0xc0, 0x6f);
647     glBegin(GL_QUADS);
648     glVertex2f(1+PIANOROLL_X+piano_porta_drag*PIANOROLL_CELLWIDTH, PIANOROLL_Y-1-(n-coct*12)*PIANOROLL_CELLHEIGHT);
649     glVertex2f(PIANOROLL_X+(piano_porta_drag+piano_porta_drag_len)*PIANOROLL_CELLWIDTH, PIANOROLL_Y-1-(n-coct*12)*PIANOROLL_CELLHEIGHT);
650     glVertex2f(PIANOROLL_X+(piano_porta_drag+piano_porta_drag_len)*PIANOROLL_CELLWIDTH, PIANOROLL_Y-(n-coct*12+1)*PIANOROLL_CELLHEIGHT);
651     glVertex2f(1+PIANOROLL_X+piano_porta_drag*PIANOROLL_CELLWIDTH, PIANOROLL_Y-(n-coct*12+1)*PIANOROLL_CELLHEIGHT);
652     glEnd();
653     if (piano_porta_drag_from != pattdata[cpatt][piano_start+piano_porta_drag]) {
654       n=piano_porta_drag_from;
655       glColor4ub(0x90, 0x90, 0x90, 0x6f);
656       glBegin(GL_QUADS);
657       glVertex2f(1+PIANOROLL_X+piano_porta_drag*PIANOROLL_CELLWIDTH, PIANOROLL_Y-1-(n-coct*12)*PIANOROLL_CELLHEIGHT);
658       glVertex2f(PIANOROLL_X+(piano_porta_drag+piano_porta_drag_len)*PIANOROLL_CELLWIDTH, PIANOROLL_Y-1-(n-coct*12)*PIANOROLL_CELLHEIGHT);
659       glVertex2f(PIANOROLL_X+(piano_porta_drag+piano_porta_drag_len)*PIANOROLL_CELLWIDTH, PIANOROLL_Y-(n-coct*12+1)*PIANOROLL_CELLHEIGHT);
660       glVertex2f(1+PIANOROLL_X+piano_porta_drag*PIANOROLL_CELLWIDTH, PIANOROLL_Y-(n-coct*12+1)*PIANOROLL_CELLHEIGHT);
661       glEnd();
662     }
663   }
664 
665   // draw the new note being dragged onto piano roll
666   if (piano_drag >= 0) {
667     n=pattdata[cpatt][piano_start+piano_drag];
668     glColor4ub(0xc0, 0xc0, 0xc0, 0x6f);
669     glBegin(GL_QUADS);
670     glVertex2f(1+PIANOROLL_X+piano_drag*PIANOROLL_CELLWIDTH, PIANOROLL_Y-1-(n-coct*12)*PIANOROLL_CELLHEIGHT);
671     glVertex2f(PIANOROLL_X+(piano_dragto+1)*PIANOROLL_CELLWIDTH, PIANOROLL_Y-1-(n-coct*12)*PIANOROLL_CELLHEIGHT);
672     glVertex2f(PIANOROLL_X+(piano_dragto+1)*PIANOROLL_CELLWIDTH, PIANOROLL_Y-(n-coct*12+1)*PIANOROLL_CELLHEIGHT);
673     glVertex2f(1+PIANOROLL_X+piano_drag*PIANOROLL_CELLWIDTH, PIANOROLL_Y-(n-coct*12+1)*PIANOROLL_CELLHEIGHT);
674     glEnd();
675   }
676 
677   // highlight the note row if a key is currently down
678   if (kpkeydown >=0 && kpkeydown>=(coct*12) && kpkeydown<((coct+PIANOROLL_OCTAVES)*12)) {
679     glColor4ub(0xc0, 0xc0, 0xc0, 0x3f);
680     glBegin(GL_QUADS);
681 
682           glVertex2f(PIANOROLL_X,
683                      PIANOROLL_Y-1-(kpkeydown-coct*12)*PIANOROLL_CELLHEIGHT);
684 
685           glVertex2f(PIANOROLL_X+(pattlen[cpatt]*(beats_per_measure*beatdiv)-piano_start)*PIANOROLL_CELLWIDTH,
686                      PIANOROLL_Y-1-(kpkeydown-coct*12)*PIANOROLL_CELLHEIGHT);
687 
688           glVertex2f(PIANOROLL_X+(pattlen[cpatt]*(beats_per_measure*beatdiv)-piano_start)*PIANOROLL_CELLWIDTH,
689                      PIANOROLL_Y-(kpkeydown-coct*12+1)*PIANOROLL_CELLHEIGHT);
690 
691           glVertex2f(PIANOROLL_X,
692                      PIANOROLL_Y-(kpkeydown-coct*12+1)*PIANOROLL_CELLHEIGHT);
693 
694     glEnd();
695   }
696 
697   // draw the horizontal slider
698   draw_hslider(PIANOROLL_X, PIANOROLL_Y+12, (DS_WIDTH-(PIANOROLL_X+6)), 12,
699    piano_start, (DS_WIDTH-(PIANOROLL_X+4))/PIANOROLL_CELLWIDTH,
700    pattlen[cpatt]*(beats_per_measure*beatdiv),
701    slide_hover);
702 
703   // draw the ui elements on the pattern page
704   draw_button(14, DS_HEIGHT-14, 16, "<<", patt_ui[B_PREV]);
705   sprintf(tmps, "%02d", cpatt);
706   draw_textbox(39, DS_HEIGHT-14, 16, 24, tmps, 0);
707   draw_button(64, DS_HEIGHT-14, 16, ">>", patt_ui[B_NEXT]);
708 
709   draw_button(98, DS_HEIGHT-14, 16, "<<", patt_ui[B_SHORTER]);
710   sprintf(tmps, "%1d meas", (unsigned int)(pattlen[cpatt]));
711   draw_textbox(139, DS_HEIGHT-14, 16, 56, tmps, 0);
712   draw_button(180, DS_HEIGHT-14, 16, ">>", patt_ui[B_LONGER]);
713 
714   draw_button(12, PIANOROLL_Y+15.5, 16, "DN", patt_ui[B_OCTDN]);
715   draw_button(32, PIANOROLL_Y+15.5, 16, "UP", patt_ui[B_OCTUP]);
716 
717   draw_button(214, DS_HEIGHT-14, 16, "<<", patt_ui[B_PREVSYN]);
718   sprintf(tmps, "%02d:%s", csynth[cpatch], patchname[csynth][csynth[cpatch]]);
719   draw_textbox(322, DS_HEIGHT-14, 16, 188, tmps, patt_ui[B_SYNNAME]);
720   draw_button(430, DS_HEIGHT-14, 16, ">>", patt_ui[B_NEXTSYN]);
721 
722   ticks=playpos / (OUTPUTFREQ/(bpm*256/60));
723   if (audiomode==AUDIOMODE_PATTERNPLAY) {
724     sprintf(tmps, "%02ld:%02ld", (ticks>>10), ((ticks>>6)&15));
725   } else {
726     sprintf(tmps, "play");
727   }
728   draw_textbox(482, DS_HEIGHT-14, 16, 42, tmps, patt_ui[B_PATTPLAY]);
729 
730   draw_button(555, DS_HEIGHT-14, 16, "X", patt_ui[B_PATTCLEAR]);
731 
732   draw_button(622, DS_HEIGHT-14, 16, "C", patt_ui[B_COPY]);
733   draw_button(644, DS_HEIGHT-14, 16, "V", patt_ui[B_PASTE]);
734   if (patt_clipboard_len==0) {
735     glColor4f(0,0,0,0.4f);
736     glBegin(GL_QUADS);
737     glVertex2f(644-8, (DS_HEIGHT-14)-8);  glVertex2f(644+8, (DS_HEIGHT-14)-8);
738     glVertex2f(644+8, (DS_HEIGHT-14)+8);  glVertex2f(644-8, (DS_HEIGHT-14)+8);
739     glEnd();
740   }
741 
742 }
743