// Blue Notes // By Karl Hörnell, May 1, 1996 // Last updated May 19 import java.awt.*; import java.applet.AudioClip; import java.awt.image.*; import java.net.*; public final class bluenotes extends java.applet.Applet implements Runnable { int i,j,k; int soundCounter=0,lastPlayed=-1; // Used when playing int hPos,bar,barPix; int marker=0,tempMarker,speed=65,tempSpeed; // Temporary and actual control variables int currX=0,currY=0; // Cursor position holders int dragged,beginX,beginY; // Type of drag and where it started int playHere,playWait,rotCount; // Used by play loop int lastBar; // Final bar of melody int moveDur,moveVal,movePos,moveHeight,newVal,stripX; // Used when dragging note Color lightBlue,colSet[]; MediaTracker tracker; Thread playThread; AudioClip notes[]; Image lines,strip,stripBuf,controls,rotNotes[]; Graphics linesG,stripG,stripBufG,controlsG,tempG; long keepTime,playSleep; // Used for timing Math m; int noteVal[],noteDur[]; // Note values for "Daisy, Daisy" final int defaultVal[]={17,14,10,5,7,9,10,7,10,5,-1, 12,17,14,10,7,9,10,12,14,12,-1,14, 15,14,12,17,14,12,10,-1,12,14,10,7,10,7,5,-1,5, 10,14,12,5,10,14,12,14,15,17,14,10,12,9,10,22,-1,-1,-2}; final int defaultDur[]={6,6,6,6,2,2,2,4,2,6,6, 6,6,6,6,2,2,2,4,2,6,4,2, 2,2,2,4,2,2,4,4,2,4,2,4,2,2,4,4,2, 4,2,4,2,4,2,2,2,2,2,2,2,4,2,6,2,2,2,0}; // Which notes have hash marks final int hashOn[]={0,1,0,0,1,0,1,0,0,1,0,1,0,1,0,0,1,0,1,0,0,1,0,1,0,1,0,0,1}; final int adjust[]={0,0,6,11,16,0,27,0,38}; int height[],lineThrough[],symbHeight[]; // Data used for drawing G clef, rotating note and 1/4 pause symbol int clefX[],clef2X[],quarX[]; final int quarY[]={40,44,48,51,54,59,54,50,46,43}; final int clefY[]={25,18,18,22,26,37,45,51,56,50,46,40,32,25,23,21,23}; final int clefXbase[]={12,16,21,23,23,15,10,6,4,4,5,9,14,18,19,18,15}; final int clef2Y[]={56,50,47,47,48,52,56,59,53,51,50,53}; final int clef2Xbase[]={25,24,20,14,11,10,10,12,12,15,18,22}; final int bigNoteX[]={11,18,23,25,30,31,25,22,16,24,22,17,8,3,1,0,2,8,13,17,8,11}; final int bigNoteY[]={0,7,8,10,13,18,14,16,15,40,45,49,49,46,43,39,34,31,32,34,2,0}; // Center location of notes & pause symbols of various lengths final int hAdjust[]={0,5,10,16,23,0,35,0,45}; // Screen horizontal offsets final int notesAdjust34[]={0,12,24,36,48,60}; final int notesAdjust44[]={0,12,24,36,46,58,70,82}; boolean musicOn=false,mousePressed=false,updateControls=false; boolean updateNotes=false,updateRot=false,updateStrip=false; boolean noteFlags[]; public void init() { acceptBGColor(); // Set background color (no particular reason) notes = new AudioClip[29]; // Fetch note audio clips for (j=0;j<29;j++) { notes[j]=getAudioClip(getCodeBase(),"notes/note"+j+".au"); } bar=6; // Default 3/4 settings barPix=11*bar+6; hPos=bar-4; // Where we are in the sheet noteVal=new int[650]; // Prepare miscellaneous arrays noteDur=new int[650]; quarX=new int[10]; clefX=new int[17]; clef2X=new int[12]; noteFlags=new boolean[8]; lightBlue=new Color(128,128,255); rotNotes=new Image[20]; // To hold rotating notes prepareBigNotes(); i=bar; // Prepare default tune. Ends with -2 for (j=0;defaultVal[j]!=-2;j++) { noteVal[i]=defaultVal[j]; noteDur[i]=defaultDur[j]; i+=defaultDur[j]; } lastBar=(i-1)/bar; height=new int[29]; // Compute vertical note positions j=91; for (i=0;i<29;i++) { if (hashOn[i]==0) j-=5; height[i]=j; } symbHeight=new int[29]; // Symbolic height - used when dragging notes j=91; // Hashed notes are a couple of pix higher k=0; for (i=0;i<29;i++) { if (k==0) { if (hashOn[i]==0) j-=5; else j-=2; } else j-=3; k=hashOn[i]; symbHeight[i]=j; } lineThrough=new int[29]; // Some notes outside main lines should have a lineThrough[0]=1;lineThrough[1]=1; // short line through them lineThrough[3]=1;lineThrough[4]=1; lineThrough[24]=1;lineThrough[25]=1; lineThrough[27]=1;lineThrough[28]=1; lines=createImage(376,97); // Visible note sheet linesG=lines.getGraphics(); drawNotes(); strip=createImage(11,97); // Used when dragging note. Small strip just stripG=strip.getGraphics(); // wide enough to contain one note, drawn stripBuf=createImage(11,97); // onto the main sheet when dragging, in stripBufG=stripBuf.getGraphics(); // order to save time stripBufG.setColor(Color.white); stripBufG.fillRect(0,0,11,97); stripBufG.setColor(Color.black); for (i=0;i<5;i++) stripBufG.drawLine(0,29+i*10,11,29+i*10); controls=createImage(376,50); controlsG=controls.getGraphics(); prepareControlPanel(); resize(400,170); } public void prepareBigNotes() // Construct images of rotating notes { Color colSet[]; int tempNoteX[]; tempNoteX=new int[22]; colSet=new Color[10]; for (j=0;j<10;j++) colSet[j]=new Color(16*j,16*j,165+10*j); for (j=0;j<20;j++) { rotNotes[j]=createImage(32,50); tempG=rotNotes[j].getGraphics(); tempG.setColor(lightBlue); tempG.fillRect(0,0,32,50); for (k=0;k<22;k++) { tempNoteX[k]=(int)(16+m.cos(0.31416*j)*(bigNoteX[k]-16)); } tempG.setColor(colSet[(27-j)%10]); tempG.fillPolygon(tempNoteX,bigNoteY,22); tempG.setColor(Color.black); tempG.drawPolygon(tempNoteX,bigNoteY,22); } } public void prepareControlPanel() // Draw controls (buttons & stuff) { controlsG.setColor(lightBlue); controlsG.fillRect(0,0,376,50); controlsG.fill3DRect(0,22,45,24,true); controlsG.fill3DRect(50,22,45,24,false); controlsG.fill3DRect(230,22,45,24,true); controlsG.fill3DRect(280,22,28,24,(bar==8)); controlsG.fill3DRect(313,22,28,24,(bar==6)); controlsG.fill3DRect(17,0,308,14,false); controlsG.fill3DRect(0,0,14,14,true); controlsG.fill3DRect(328,0,14,14,true); controlsG.fill3DRect(112,37,102,8,false); controlsG.setColor(Color.white); controlsG.fillRect(18,1,306,12); controlsG.setColor(Color.black); controlsG.fillRect(113,38,100,6); controlsG.drawString("PLAY",12,39); controlsG.drawString("STOP",62,39); controlsG.drawString("CLEAR",238,39); controlsG.drawString("3/4",285,39); controlsG.drawString("4/4",317,39); controlsG.drawString("<",4,12); controlsG.drawString(">",333,12); controlsG.drawString("SPEED",112,29); controlsG.setColor(Color.blue); controlsG.fill3DRect(18+marker,1,16,12,true); controlsG.fill3DRect(111+speed,32,10,18,true); controlsG.drawImage(rotNotes[0],344,0,this); } public void acceptBGColor() // Convert hexadecimal RGB parameter to color { int hex[]; String s,h="0123456789abcdef"; Color c; hex=new int[6]; s=getParameter("bgcolor"); if ((s!=null)&&(s.length()==6)) { for (i=0;i<6;i++) for (j=0;j<16;j++) if (s.charAt(i)==h.charAt(j)) hex[i]=j; c=new Color(hex[0]*16+hex[1],hex[2]*16+hex[3],hex[4]*16+hex[5]); } else c=Color.lightGray; // Default setBackground(c); } public void drawNotes() // Fix the note sheet { int i,j,k,x; linesG.setColor(Color.white); linesG.fillRect(0,0,376,97); linesG.setColor(Color.black); // First draw the lines for (i=0;i<5;i++) linesG.drawLine(0,29+i*10,376,29+i*10); if (bar==8) j=-notesAdjust44[hPos % bar]; else j=-notesAdjust34[hPos % bar]; while (j<376) { linesG.drawLine(j-1,29,j-1,69); j+=barPix; } if (hPos>=bar) j=hPos-bar; else // Far enough left that the G clef is visible? Draw it { j=0; // Draw G clef and other things if (bar==8) x=barPix-notesAdjust44[hPos]-45; else x=barPix-notesAdjust34[hPos]-45; linesG.fillOval(x+5,71,6,6); linesG.drawArc(x+4,66,14,12,-130,170); linesG.drawLine(x+18,69,x+13,25); linesG.drawLine(x-3,29,x-3,69); linesG.drawLine(x-2,29,x-2,69); for (i=0;i<17;i++) clefX[i]=clefXbase[i]+x; for (i=0;i<12;i++) clef2X[i]=clef2Xbase[i]+x; linesG.fillPolygon(clefX,clefY,17); linesG.fillPolygon(clef2X,clef2Y,12); linesG.drawArc(x+3,42,22,24,-180,200); linesG.drawString(""+bar/2,x+32,49); linesG.drawString("4",x+32,59); } j=bar*(j / bar); // Determine sheet horizontal offset, start a bit out left if (bar==8) x=-notesAdjust44[(hPos-j) % bar]-barPix*((hPos-j)/bar); else x=-notesAdjust34[(hPos-j) % bar]-barPix*((hPos-j)/bar); while (j<(hPos+32)) // Keep drawing until right part of screen reached { if ((j % bar)==0) // Get the 1/8 notes properly tied together fixEights(j,x); if (noteDur[j]>0) // Draw each individual note placeNote(noteVal[j],noteDur[j], x+hAdjust[noteDur[j]]+11*(j % bar),noteFlags[j % bar],linesG); j++; if ((j % bar)==0) x+=barPix; } } // Note-drawing routine. Used both when updating sheet and fixing the drag strip public void placeNote(int val,int dur,int x,boolean flag,Graphics g) { int y; g.setColor(Color.black); if (val>=0) // Real note { y=height[val]; // Various rules for which notes have flags, dots, etc. if (lineThrough[val]==1) g.drawLine(x-2,y+3,x+8,y+3); g.setColor(Color.blue); if ((dur>0)&&(dur<4)) g.fillOval(x-1,y,7,6); if ((dur==1)&& flag) { if (val<14) { g.drawLine(x+5,y-11,x+8,y-6); g.drawLine(x+8,y-6,x+5,y-9); } else { g.drawLine(x,y+16,x+3,y+12); g.drawLine(x+3,y+12,x,y+14); } } if ((dur>0)&&(dur<8)&& flag) if (val<14) g.drawLine(x+5,y+3,x+5,y-12); else g.drawLine(x-1,y+3,x-1,y+17); if (dur>3) g.drawOval(x-1,y,6,5); if ((dur==3)||(dur==6)) { g.drawLine(x+7,y+2,x+8,y+2); g.drawLine(x+7,y+3,x+8,y+3); } if (hashOn[val]==1) { g.drawLine(x-2,y-1,x+3,y-1); g.drawLine(x-2,y-3,x+3,y-3); g.drawLine(x-1,y,x-1,y-4); g.drawLine(x+1,y,x+1,y-4); } } else // Pause mark { switch(dur) { case 1: // 1/8 g.fillRect(x,43,3,3); g.drawLine(x+3,45,x+6,43); g.drawLine(x+6,43,x+1,54); break; case 2: // 1/4 quarX[0]=x+3; quarX[1]=x+6; quarX[2]=x+4; quarX[3]=x+7; quarX[4]=x+4; quarX[5]=x+4; quarX[6]=x+2; quarX[7]=x+4; quarX[8]=x+2; quarX[9]=x+4; g.fillPolygon(quarX,quarY,10); break; case 4: // 1/2 g.fillRect(x+1,46,6,3); break; case 6: // 3/4 (special case) g.fillRect(x+1,40,6,3); break; case 8: // 1/1 g.fillRect(x+1,40,6,3); break; default: break; } } } // Find which vertical note position the cursor points to public int nearestNote(int y,boolean hashed) { int j,k,near,h; k=-1; if (hashed) h=1; else h=0; near=9; for (j=0;j<29;j++) if (((height[j]-y)*(height[j]-y)12)&&(x<388)&&(y>11)&&(y<108)) { relX=x-12+(int)(hPos*((double)barPix)/bar); } return relX; } public int locateNote(int x,int y) // Which note is pointed at? { int i,j,k,noteNum=-1; // Default none i=(x/barPix)*bar; j=x-((x/barPix)*barPix)-2; for (k=0;k0)&&(noteVal[i+k]>=0)) if ((j-(hAdjust[noteDur[i+k]]+11*k))*(j-(hAdjust[noteDur[i+k]]+11*k))+ (y-2-height[noteVal[i+k]])*(y-2-height[noteVal[i+k]])<15) noteNum=i+k; return noteNum; } public void fixStrip(int val,int dur) // Draw a single note onto drag strip { stripG.drawImage(stripBuf,0,0,this); placeNote(val,dur,2,true,stripG); } // Place a new note on the sheet if there is room for it public void newNote(int x,int y,int hPos,int dur,boolean hashed) { int i,j,k,nx,count,buffer[]={-1,-1,-1,-1,-1,-1,-1,-1}; int val,bestOK=-1,nearest=100; nx=findNoteX(x,y,hPos); val=nearestNote(y-14,hashed); if ((nx>=barPix)&&(val>=0)) { i=(nx/barPix)*bar; j=nx-((nx/barPix)*barPix)-2; k=0; while (k=0)&&(noteDur[i+k]>0)) k+=noteDur[i+k]; else { buffer[k]=0; k++; } } k=bar-1; // Compute number of free positions to the left count=0; while (k>=0) { if (buffer[k]==0) { count++; buffer[k]=count; } else count=0; k--; } for (k=0;k=dur)&&((j-(hAdjust[dur]+11*k))* (j-(hAdjust[dur]+11*k))=0) { noteDur[i+bestOK]=dur; noteVal[i+bestOK]=val; k=lastBar+1; while (k<(i/bar)) // Fill out empty bars with pause marks { noteDur[k*bar]=bar; noteVal[k*bar]=-1; k++; } if ((i/bar)>lastBar) lastBar=i/bar; fixPauses(nx); drawNotes(); updateNotes=true; repaint(); } } } // Fix the appropriate pause marks in a given bar public void fixPauses(int x) { int i,j,k,length,loc; boolean finished=false; int buffer[]={0,0,0,0,0,0,0,0,0}; i=(x/barPix)*bar; // First remove all old pauses for (j=0;j0)&&(loc>=0)) { noteVal[i+loc]=-1; noteDur[i+loc]=length; loc=-1; } j++; length++; } while (!finished) // Lots of rules for how to split up { // pauses into smaller parts j=0; while (j=0;j--) { if ((noteDur[pos+j]==1)&&(noteVal[pos+j]>=0)) k++; else { if (k>0) { buffer[j+1]=k; k=0; } } } if (k>0) buffer[0]=k; for (i=0;i 4)) // Needs splitting { buffer[4]=buffer[j]+j-4; buffer[j]=4-j; } if (buffer[j]>1) // Compute least-squares line fit { avg=0; slope=0; s=-5.5*(buffer[j]-1); t=0.0; for (i=pos+j;i<(pos+j+buffer[j]);i++) { avg=avg+height[noteVal[i]]; slope=slope+height[noteVal[i]]*s; t=t+s*s; s=s+11.0; } avg=avg/buffer[j]; slope=slope/t; // Check if it will look OK. Sometimes they are too uneven s=-5.5*(buffer[j]-1); t=0; for (i=pos+j;i<(pos+j+buffer[j]);i++) { if ((height[noteVal[i]]-(avg+s*slope))* (height[noteVal[i]]-(avg+s*slope)) > t) t=(height[noteVal[i]]-(avg+s*slope))* (height[noteVal[i]]-(avg+s*slope)); s=s+11.0; } if ((t>25)||((slope*slope)>3.1)) // No? Split again. { if (buffer[j]==2) { buffer[j]=0; } else { i=2*((j+2)/2); // Break at next even 1/4 buffer[i]=buffer[j]+j-i; buffer[j]=i-j; } } else // OK. Draw bond as filled quadrilateral, plus lines { if (avg>46) // Bond above { bondX[0]=imgX+5+j*11+5; bondY[0]=(int)(avg-slope*buffer[j]*5.5-14-2*slope*(slope-1)); bondY[1]=bondY[0]+5; } else // Bond below { bondX[0]=imgX+5+j*11; bondY[0]=(int)(avg-slope*buffer[j]*5.5+20+2*slope*(slope+1)); bondY[1]=bondY[0]-5; } bondX[1]=bondX[0]; bondX[2]=bondX[1]+1+11*(buffer[j]-1); bondX[3]=bondX[2]; bondY[2]=(int)(bondY[1]+slope*(1+11*(buffer[j]-1))); bondY[3]=(int)(bondY[0]+slope*(1+11*(buffer[j]-1))); linesG.setColor(Color.blue); linesG.fillPolygon(bondX,bondY,4); p=bondX[0]; q=(bondY[0]+bondY[1])/2; for (i=pos+j;i<(pos+j+buffer[j]);i++) { linesG.drawLine(p,q,p,height[noteVal[i]]+3); t=t+11; p+=11; q+=(int)(11*slope); } for (i=j;i<(j+buffer[j]);i++) noteFlags[i]=false; buffer[j]=0; } } } } } // Various user input routines public boolean mouseDown(java.awt.Event e,int x, int y) { int i,j,pickedNote; if (!musicOn) { int nx=findNoteX(x,y,hPos); // Cursor x position if within sheet mousePressed=true; dragged=0; tempMarker=marker; tempSpeed=speed; beginX=x; beginY=y; if ((x>(27+marker))&&(x<(44+marker))&&(y>115)&&(y<128)) dragged=1; // Marker selected if ((x>(122+speed))&&(x<(133+speed))&&(y>147)&&(y<166)) dragged=2; // Speed control selected if ((x>12)&&(x<27)&&(y>115)&&(y<128)) // Left arrow clicked { if (marker>0) tempMarker=marker-1; } if ((x>340)&&(x<355)&&(y>115)&&(y<128)) // Right arrow clicked { if (marker<290) tempMarker=marker+1; } if ((x>242)&&(x<288)&&(y>137)&&(y<162)) // Clear? { for (i=1;i<=lastBar;i++) for (j=0;j292)&&(x<321)&&(y>137)&&(y<162)) // 3/4 button pressed if ((lastBar==0)&&(bar==8)) // Works only if the sheet is cleared { bar=6; barPix=6+11*bar; hPos-=2; drawNotes(); controlsG.setColor(lightBlue); controlsG.fill3DRect(280,22,28,24,false); controlsG.fill3DRect(313,22,28,24,true); controlsG.setColor(Color.black); controlsG.drawString("3/4",285,39); controlsG.drawString("4/4",317,39); updateNotes=true; updateNotes=true; updateControls=true; repaint(); } if ((x>325)&&(x<354)&&(y>137)&&(y<162)) // 4/4 button pressed if ((lastBar==0)&&(bar==6)) // Works only if the sheet is cleared { bar=8; barPix=6+11*bar; hPos+=2; drawNotes(); controlsG.setColor(lightBlue); controlsG.fill3DRect(280,22,28,24,true); controlsG.fill3DRect(313,22,28,24,false); controlsG.setColor(Color.black); controlsG.drawString("3/4",285,39); controlsG.drawString("4/4",317,39); updateNotes=true; updateControls=true; repaint(); } if ((x>12)&&(x<58)&&(y>136)&&(y<160)) // Start music? { playHere=bar; playWait=0; rotCount=0; playSleep=220-2*speed; playThread=new Thread(this,"PlayIt"); playThread.start(); musicOn=true; controlsG.setColor(lightBlue); controlsG.fill3DRect(0,22,45,24,false); controlsG.fill3DRect(50,22,45,24,true); controlsG.setColor(Color.black); controlsG.drawString("PLAY",12,39); controlsG.drawString("STOP",62,39); updateControls=true; repaint(); } if (nx>=0) { pickedNote=locateNote(nx,y-11); // Note selected? Prepare for dragging if (pickedNote>=0) { dragged=3; moveDur=noteDur[pickedNote]; moveVal=noteVal[pickedNote]; newVal=moveVal; moveHeight=symbHeight[moveVal]; movePos=pickedNote; noteDur[pickedNote]=0; noteVal[pickedNote]=0; // Determine screen horizontal position of the drag strip if (bar==6) stripX=12+barPix*(movePos / bar)+11*(movePos % bar) -notesAdjust34[hPos % bar]+hAdjust[moveDur] -(hPos / bar)*barPix-2; else stripX=12+barPix*(movePos / bar)+11*(movePos % bar) -notesAdjust44[hPos % bar]+hAdjust[moveDur] -(hPos / bar)*barPix-2; updateNotes=true; updateStrip=true; drawNotes(); fixStrip(moveVal,moveDur); repaint(); } } } else { if ((x>62)&&(x<108)&&(y>136)&&(y<160)) // Stop music? { if ((playThread!=null)&&(playThread.isAlive())) { playThread.stop(); } playThread=null; musicOn=false; controlsG.setColor(lightBlue); controlsG.fill3DRect(0,22,45,24,true); controlsG.fill3DRect(50,22,45,24,false); controlsG.setColor(Color.black); controlsG.drawString("PLAY",12,39); controlsG.drawString("STOP",62,39); updateControls=true; repaint(); } } return false; } public boolean mouseUp(java.awt.Event e,int x, int y) { mousePressed=false; marker=tempMarker; speed=tempSpeed; if (hPos!=(2*marker+bar-4)) // Has the marker moved? { hPos=2*marker+bar-4; drawNotes(); // Draw new notes controlsG.setColor(Color.white); controlsG.fillRect(18,1,306,12); controlsG.setColor(Color.blue); controlsG.fill3DRect(18+tempMarker,1,16,12,true); updateControls=true; updateNotes=true; repaint(); } if (dragged==3) // Letting go of dragged note? { if (newVal<0) // Dragged off screen? Tidy up fixPauses(findNoteX(beginX,beginY,hPos)); else // Put in new note { noteVal[movePos]=newVal; noteDur[movePos]=moveDur; } drawNotes(); updateNotes=true; updateStrip=false; repaint(); } dragged=0; return false; } public boolean mouseDrag(java.awt.Event e,int x, int y) { int i,j; currX=x; currY=y; switch (dragged) { case 1: // Marker controlsG.setColor(Color.white); controlsG.fillRect(18+tempMarker,1,16,12); if ((x-beginX+marker)>290) tempMarker=290; else if ((x-beginX+marker)<0) tempMarker=0; else tempMarker=x-beginX+marker; controlsG.setColor(Color.blue); controlsG.fill3DRect(18+tempMarker,1,16,12,true); updateControls=true; repaint(); break; case 2: // Speed control controlsG.setColor(lightBlue); controlsG.fillRect(110,32,106,18); controlsG.fill3DRect(112,37,102,8,false); controlsG.setColor(Color.black); controlsG.fillRect(113,38,100,6); if ((x-beginX+speed)>95) tempSpeed=95; else if ((x-beginX+speed)<0) tempSpeed=0; else tempSpeed=x-beginX+speed; controlsG.setColor(Color.blue); controlsG.fill3DRect(111+tempSpeed,32,10,18,true); updateControls=true; repaint(); break; case 3: // Dragged note // Draw new note onto drag strip newVal=-1; i=25; for (j=0;j<29;j++) { if ((moveHeight+currY-beginY-symbHeight[j])* (moveHeight+currY-beginY-symbHeight[j])=0) { if (lastPlayed>=0) notes[lastPlayed].stop(); lastPlayed=i; notes[i].play(); } break; case 100: // Delete note x=findNoteX(currX,currY,hPos); if (x>-1) { pickedNote=locateNote(x,currY-11); if (pickedNote>=0) { noteVal[pickedNote]=0; noteDur[pickedNote]=0; fixPauses(x); updateNotes=true; drawNotes(); repaint(); } } break; case 49: // 1/8 Insert new note newNote(currX,currY,hPos,1,mousePressed); break; case 50: // 1/4 newNote(currX,currY,hPos,2,mousePressed); break; case 51: // 3/8 newNote(currX,currY,hPos,3,mousePressed); break; case 52: // 1/2 newNote(currX,currY,hPos,4,mousePressed); break; case 54: // 3/4 newNote(currX,currY,hPos,6,mousePressed); break; case 56: // 1/1 newNote(currX,currY,hPos,8,mousePressed); break; default: break; } return false; } public void run() // Thread loop. Plays music and animates note. { while ((playThread !=null)&&((noteDur[playHere]+playWait)>0)) { keepTime=System.currentTimeMillis(); if (playWait==0) { if (lastPlayed>=0) notes[lastPlayed].stop(); // Stop last note if (noteVal[playHere]>=0) notes[noteVal[playHere]].play(); // Play new note lastPlayed=noteVal[playHere]; playWait=noteDur[playHere]; // Get number of 1/8 it lasts } playWait--; playHere++; updateRot=true; rotCount++; repaint(); try { playThread.sleep(m.max(0,keepTime+playSleep- System.currentTimeMillis())); } catch (InterruptedException e) {} } musicOn=false; // Finished. Restore control area controlsG.setColor(lightBlue); controlsG.fill3DRect(0,22,45,24,true); controlsG.fill3DRect(50,22,45,24,false); controlsG.setColor(Color.black); controlsG.drawString("PLAY",12,39); controlsG.drawString("STOP",62,39); updateControls=true; repaint(); } public void stop() // Stop playing music { if ((playThread!=null)&&(playThread.isAlive())) { playThread.stop(); } playThread=null; musicOn=false; } public void paint(Graphics g) // Draw everything (after initializing) { g.setColor(lightBlue); g.fill3DRect(0,0,400,170,true); g.draw3DRect(11,10,377,98,false); g.drawImage(lines,12,11,this); g.drawImage(controls,12,115,this); } public void update(Graphics g) // Update individual parts { if (updateControls) g.drawImage(controls,12,115,this); if (updateNotes) g.drawImage(lines,12,11,this); if (updateRot) g.drawImage(rotNotes[rotCount % 20],356,115,this); if (updateStrip) g.drawImage(strip,stripX,11,this); updateNotes=false; updateControls=false; updateRot=false; updateStrip=false; } }