// 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)<near)&&(hashOn[j]==h))
				{
					near=(height[j]-y)*(height[j]-y);
					k=j;
				}
		return k;
	}

// Convert cursor co-ordinate to x-coordinate within the note sheet
	public int findNoteX(int x,int y,int hPos)
	{
		int relX=-1;
		
		if ((x>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;k<bar;k++)
			if ((noteDur[i+k]>0)&&(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<bar)
			{
				if ((noteVal[i+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<bar;k++)
				if ((buffer[k]>=dur)&&((j-(hAdjust[dur]+11*k))*
					(j-(hAdjust[dur]+11*k))<nearest))
				{
					nearest=(j-(hAdjust[dur]+11*k))*
						(j-(hAdjust[dur]+11*k));
					bestOK=k;
				}
			if (bestOK>=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;j<bar;j++)
		{
			if (noteVal[i+j]==-1)
			{
				noteVal[i+j]=0;
				noteDur[i+j]=0;
			}
			for (k=0;k<noteDur[i+j];k++)
				buffer[j+k]=1;
		}
		j=0; // Prepare to construct new ones. Start with raw values.
		loc=-1;
		length=0;
		buffer[bar]=1;
		while (j<=bar)
		{
			if ((buffer[j]==0)&&(loc<0))
			{
				length=0;
				loc=j;
			}
			if ((buffer[j]==1)&&(length>0)&&(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<bar)
			{
				if ((buffer[j]==0)&&(noteVal[i+j]==-1))
				{
					switch(noteDur[i+j])
					{
						case 1:
							buffer[j]=1;
							break;
						case 2:
							buffer[j]=1;
							buffer[j+1]=1;
							break;
						case 3:
							if ((j % 2)==0) // Even
							{
								noteDur[i+j]=2;
								noteDur[i+j+2]=1;
								noteVal[i+j+2]=-1;
							}
							else // Odd
							{
								noteDur[i+j]=1;
								noteDur[i+j+1]=2;
								noteVal[i+j+1]=-1;
							}
							break;
						case 4:
							if (bar==8) // 1/2 OK
								for (k=0;k<4;k++)
									buffer[j+k]=1;
							else
							{
								noteDur[i+j]=2;
								noteDur[i+j+2]=2;
								noteVal[i+j+2]=-1;							
							}
							break;
						case 5:
							if ((j % 2)==0) // Even
							{
								noteDur[i+j]=4;
								noteDur[i+j+4]=1;
								noteVal[i+j+4]=-1;
							}
							else // Odd
							{
								noteDur[i+j]=1;
								noteDur[i+j+1]=4;
								noteVal[i+j+1]=-1;
							}
							break;
						case 6:
							if (bar==6) // 2/3 OK
								for (k=0;k<6;k++)
									buffer[j+k]=1;
							else
							{
								if ((j % 2)==0) // Even
								{
									if ((j % 4)==2) // Right part
									{
										noteDur[i+j]=2;
										noteDur[i+j+2]=4;
										noteVal[i+j+2]=-1;
									}
									else // Left part
									{
										noteDur[i+j]=4;
										noteDur[i+j+4]=2;
										noteVal[i+j+4]=-1;
									}
								}
								else // Split into 1/8 1/2 1/8
								{
									noteDur[i+j]=1;
									noteDur[i+j+1]=4;
									noteVal[i+j+1]=-1;
									noteDur[i+j+5]=1;
									noteVal[i+j+1]=-1;
								}							
							}
							break;
						case 7:
							if ((j % 2)==0) // Even
							{
								noteDur[i+j]=6;
								noteDur[i+j+6]=1;
								noteVal[i+j+6]=-1;
							}
							else // Odd
							{
								noteDur[i+j]=1;
								noteDur[i+j+1]=6;
								noteVal[i+j+1]=-1;
							}
							break;
						case 8: // This is OK
							for (k=0;k<8;k++)
								buffer[j+k]=1;
							break;
						default:
							break;							
					}
				}
				j++;
			}
			k=0;
			for (j=0;j<bar;j++)
				if (buffer[j]==1)
					k++;
			if (k==bar) // Buffer filled. All done.
				finished=true;
		}
		while ((noteDur[i]==bar)&&(noteVal[i]==-1)&&((i/bar)==lastBar)) // Clear up
		{
			noteDur[i]=0;
			noteVal[i]=0;
			i-=bar;
			lastBar--;
		}
	}

	public void fixEights(int pos,int imgX) // Compute how to tie 1/8 notes together
	{
		int i,j,k,p,q;
		double avg,slope,s,t;
		int buffer[]={0,0,0,0,0,0,0,0,0};
		int bondX[]={0,0,0,0},bondY[]={0,0,0,0};
		
		// Keep count of how many and which ones stick together
		k=0;
		for (j=bar-1;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<bar;i++)
			noteFlags[i]=true;
		j=0;
		while (j<bar) // Do all the hard work, starting at left side of bar
		{
			if (buffer[j]<=1)
			{
				j++;
			}
			else
			{
				if ((j<4)&&((j+buffer[j]) > 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;j<bar;j++)
					{
						noteVal[i*bar+j]=0;
						noteDur[i*bar+j]=0;
					}
				lastBar=0;
				drawNotes();
				updateNotes=true;
				repaint();
			}

			if ((x>292)&&(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])<i)
					{
						i=(moveHeight+currY-beginY-symbHeight[j])*
							(moveHeight+currY-beginY-symbHeight[j]);
						newVal=j;
					}	
				}
				if (newVal<0)
					fixStrip(-1,0);
				else
					fixStrip(newVal,moveDur);
				updateStrip=true;
				repaint();
				break;
			default:
				break;
		}
		return false;
	}

	public boolean mouseMove(java.awt.Event e,int x, int y)
	{
		currX=x; // Keep track of the cursor
		currY=y;
		
		return false;
	}
	
	public boolean keyDown(java.awt.Event e,int key)
	{
		int i,x,pickedNote;
		
		if (!musicOn)
			switch (key)
			{
				case 112: // Listen to sound
					i=nearestNote(currY-14,mousePressed);
					if (i>=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;
	}
}
