// Rainbow Notes
// By Karl Hornell, 1997-02-10

import java.awt.*;
import java.awt.image.*;
import java.net.*;
import java.io.*;
import sun.audio.*;

public final class rainbow extends java.applet.Applet
{
	int i,j,k,l,m,n,o,p;
	double sc;
	boolean dragging=false,drawStrip=false;
	int freeRight[]=new int[9],occupied[][]=new int[2][8];
	int maxVol[]=new int[7],lCount[]=new int[7]; 
	byte noteData[][]=new byte[15][720],audioTable[]=new byte[16384];
	byte rawAudio[]=new byte[10],fileBuffer[]=new byte[2048],btemp;
	char outChars[]=new char[65];
	double waveData[][]=new double[7][64];
	double frequencies[]=new double[72];
	int scrollPos=0,oldScrollPos=0,barLength=8,lastBar=1,lastNote=-1,currCol=0,mx=0,my=0;
	int stripX=0,dx,selectedVal=0,selectedCol=0,selectedLen=0,selectedPos=0,barStart=0;
	int bufLength=0,bufPos=0,oldLength=0;
	final int colVals[]={255,0,0, 255,128,0, 255,255,0, 0,255,0, 120,140,255, 0,0,255, 128,0,128, 0,0,0};
	final int opaque=0xff000000;
	final int noteH[]={141,138,138,134,134,131,127,127,124,124,120,120,117,
		113,113,110,110,106,103,103,99,99,96,96,92,89,89,85,85,82,78,78,
		-100,-100,-100,-100,
		74,74,71,71,67,64,64,60,60,57,53,53,50,50,46,46,43,39,39,36,36,32,
		29,29,25,25,22,22,18,15,15,11,11,8,4,4};
	final int lines[]={137,137,137,130,130,130,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,
		88,88,88,88,81,81,81,74,74,0,0,  70,70,70,70,63,63,63,0,0,0,0,0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,0,21,21,21,14,14,14,14,7,7,7};
	final int noteHash[]={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,0,1,0,0,1,0,1,0,1,
		0,1,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,0,1,0,0,1};
	final int lengthOffset[]={0,0,8,16,24,0,40,0,56},pauseOffset[]={0,0,8,24,56};
	final int lengthVal[]={0,0,2,4,6,0,8,0,10};
	int noteDH[]=new int[72],flipOffset[]=new int[72];
	int waveforms[][]=new int[7][64],envelopes[][]=new int[7][64];
	int waveCopy[][]=new int[7][64],envCopy[][]=new int[7][64];
	final String colNames="ROYGBIV";
	String s,s1;
	Scrollbar bar,speedBar;
	Label labels[]=new Label[3];
	ActiveCanvas waveC,envC;
	Choice inFiles=new Choice(),trueInFiles=new Choice();
	Panel sevenNotes,sevenAndText,mainButtons,buttonPlusBar,allOfIt;
	Panel specialHolder,canvasHolder,midCHolder,speedHolder,choiceHolder;
	Button noteB[]=new Button[7],controlB[]=new Button[9],extraB;
	Color noteCol[]=new Color[7];
	Font smallText;
	Image splash1,splash2,offImage,clefs,paces[]=new Image[2],pauses[]=new Image[4];
	Image clefIm,offImage2,notePics[][]=new Image[7][24],strip,strip2;
	Graphics offG,offG2,clefG,sG,sG2;
	InputStream fileInput,soundStream=new AudioDataStream(new AudioData(rawAudio));;

  public void init()
  {
	for (i=0;i<72;i++)
		if ((i<15)||((i>35)&&(i<52)))
		{
			noteDH[i]=-21;
			flipOffset[i]=0;
		}
		else
		{
			noteDH[i]=-3;
			flipOffset[i]=12;
		}
	s=getParameter("file1");
	i=1;
	while (s!=null)
	{
		s1=getParameter("title"+i);
		if (s1!=null)
			inFiles.addItem(s1);
		else
			inFiles.addItem(s);
		i++;
		trueInFiles.addItem(s);
		s=getParameter("file"+i);
	}

  	smallText=new Font("Helvetica",Font.PLAIN,10);
	labels[0]=new Label("Speed");
	labels[1]=new Label("Waveform");
	labels[2]=new Label("Envelope");
	for (i=0;i<3;i++)
	{
		labels[i].setFont(smallText);
		labels[i].setForeground(Color.white);
		labels[i].setAlignment(Label.RIGHT);
	}
	labels[1].setAlignment(Label.LEFT);
    for (i=0;i<7;i++)
	{
		noteCol[i]=new Color(colVals[i*3],colVals[i*3+1],colVals[i*3+2]);
	}
    colVals[21]=Color.gray.getRed();
    colVals[22]=Color.gray.getGreen();
    colVals[23]=Color.gray.getBlue();
	
	prepareGraphics();
	System.gc();
	fixAudioBytes();
	
	for (i=0;i<64;i++)
	{
		if (i<32)
			waveforms[0][i]=29;
		else
			waveforms[0][i]=-30;
		waveforms[1][i]=(int)Math.abs(59.9*(32-i)/32.0)-30;
		waveforms[2][i]=(int)(29.9-i*0.95);
		waveforms[3][i]=(int)(30*Math.sin(0.0981*i));
		waveforms[4][i]=(int)(20*Math.sin(0.0981*i)+15*Math.sin(0.196*i));
		if (i<21)
			waveforms[5][i]=29;
		else if (i>43)
			waveforms[5][i]=-30;
		else
			waveforms[5][i]=0;
		waveforms[6][i]=(int)(50.9*Math.random()-30);
	}
	for (i=0;i<7;i++)
		for (j=0;j<64;j++)
		{
			envelopes[i][j]=4032-64*j;
		}
	for (i=0;i<7;i++)
		for (j=0;j<64;j++)
		{
			envCopy[i][j]=envelopes[i][j];
			waveCopy[i][j]=waveforms[i][j];
		}
	for (i=0;i<36;i++)
	{
		frequencies[i]=123.4708*Math.pow(2.0,i/12.0);
		frequencies[i+36]=195.9977*Math.pow(2.0,i/12.0);
	}
    setBackground(Color.gray);
    setForeground(Color.black);
    sevenNotes=new Panel();
    sevenNotes.setLayout(new FlowLayout(FlowLayout.LEFT,1,2));
    sevenAndText=new Panel();
    sevenAndText.setLayout(new BorderLayout(0,1));
    mainButtons=new Panel();
    mainButtons.setLayout(new GridLayout(1,9,1,1));
    allOfIt=new Panel();
    allOfIt.setLayout(new BorderLayout(3,1));
    midCHolder=new Panel();
    midCHolder.setLayout(new BorderLayout());
    midCHolder.add("North",labels[1]);
    midCHolder.add("South",labels[2]);
    canvasHolder=new Panel();
    canvasHolder.setLayout(new GridLayout(1,3,1,1));
    waveC=new ActiveCanvas(noteCol[0],waveforms[0],-30,30);
    envC=new ActiveCanvas(noteCol[0],envelopes[0],0,4096);
    canvasHolder.add(waveC);
    canvasHolder.add(midCHolder);
    canvasHolder.add(envC);
    for (i=0;i<7;i++)
	{
		noteB[i]=new Button(colNames.substring(i,i+1));
		noteB[i].setBackground(noteCol[i]);
		sevenNotes.add(noteB[i]);
	}
    speedHolder=new Panel();
    speedHolder.setLayout(new BorderLayout());
    speedHolder.add("West",labels[0]);
    speedBar=new Scrollbar(Scrollbar.HORIZONTAL,15,5,55,90);
    speedHolder.add("Center",speedBar);
    sevenAndText.add("North",sevenNotes);
    sevenAndText.add("South",speedHolder);
    specialHolder=new Panel();
    specialHolder.setLayout(new BorderLayout(3,1));
    bar=new Scrollbar(Scrollbar.HORIZONTAL,0,48,0,1488);
    controlB[0]=new Button("Make");
    controlB[1]=new Button("Play");
    controlB[2]=new Button("Stop");
    controlB[3]=new Button("3/4");
    controlB[4]=new Button("4/4");
    controlB[5]=new Button("Clear");
    controlB[6]=new Button("Load");
    controlB[7]=new Button("Reset");
    controlB[8]=new Button("Save");
	for (i=0;i<9;i++)
	{
		controlB[i].setBackground(Color.lightGray);
		mainButtons.add(controlB[i]);
	}
    allOfIt.add("North",bar);
    specialHolder.add("West",sevenAndText);
    specialHolder.add("Center",canvasHolder);
    allOfIt.add("Center",specialHolder);
    allOfIt.add("South",mainButtons);
    setLayout(new BorderLayout());
	choiceHolder=new Panel();
	inFiles.setBackground(Color.lightGray);
	choiceHolder.add(inFiles);
    add("South",allOfIt);
	add("North",choiceHolder);
	choiceHolder.hide();
	computePauses(0);
	if (inFiles.countItems()>0)
		loadNotes(trueInFiles.getItem(0));
	drawPace();
	updateSheet();
  }

	public void prepareGraphics()
	{
  		int colHoriz[]=new int[210];
		int colVert[]=new int[50];
		MediaTracker mt=new MediaTracker(this);
		Image inPic = getImage(getCodeBase(),"notes.gif");
		mt.addImage(inPic,0);
		for (j=0;j<7;j++)
			for (k=0;k<30;k++)
				colHoriz[j*30+k]=(((int)(colVals[j*3]*(30-k)/30.0+colVals[j*3+3]*k/30.0))<<16) |
					(((int)(colVals[j*3+1]*(30-k)/30.0+colVals[j*3+4]*k/30.0))<<8) |
					((int)(colVals[j*3+2]*(30-k)/30.0+colVals[j*3+5]*k/30.0)) | opaque;
		splash1=createImage(new MemoryImageSource(210,1,colHoriz,0,210));
			for (k=0;k<50;k++)
				colVert[k]=(((int)(colVals[0]*(50-k)/50.0+colVals[21]*k/50.0))<<16) |
					(((int)(colVals[1]*(50-k)/50.0+colVals[22]*k/50.0))<<8) |
					((int)(colVals[2]*(50-k)/50.0+colVals[23]*k/50.0)) | opaque;
		splash2=createImage(new MemoryImageSource(1,50,colVert,0,1));
		mt.addImage(splash1,0);
		mt.addImage(splash2,0);
		try
		{
			mt.waitForID(0);
		}
			catch(InterruptedException e) {}
		int noteInts[]=new int[96*144];
		PixelGrabber pg=new PixelGrabber(inPic,0,0,96,144,noteInts,0,96);
		showStatus("Grabbing pixels");
		try
		{
			pg.grabPixels();
		}
			catch(InterruptedException e) {}
		MemoryImageSource ip=new MemoryImageSource(32,144,noteInts,64,96);			
		clefs=createImage(new FilteredImageSource(ip,
			new CropImageFilter(0,0,32,102)));
		mt.addImage(clefs,1);
		paces[0]=createImage(new FilteredImageSource(ip,
			new CropImageFilter(0,102,8,15)));
		mt.addImage(paces[0],1);
		paces[1]=createImage(new FilteredImageSource(ip,
			new CropImageFilter(8,102,8,15)));
		mt.addImage(paces[1],1);
		for (i=0;i<4;i++)
		{
			pauses[i]=createImage(new FilteredImageSource(ip,
				new CropImageFilter(i*8,120,8,24)));
			mt.addImage(pauses[i],1);
		}
		try
		{
			mt.waitForID(1);
		}
			catch(InterruptedException e) {}

		byte noteBytes[]=new byte[64*144];
		for (i=0;i<144;i++)
			for (j=0;j<64;j++)
				noteBytes[i*64+j]=(byte)(noteInts[i*96+j]&255);
		byte RGB[][]=new byte[4][256];
		for (i=0;i<4;i++)
		{
			RGB[i][255]=(byte)255;
		}
		RGB[3][0]=(byte)255;
		RGB[3][63]=(byte)255;
		for (i=0;i<7;i++)
		{
			RGB[0][63]=(byte)colVals[i*3];
			RGB[1][63]=(byte)colVals[i*3+1];
			RGB[2][63]=(byte)colVals[i*3+2];
			MemoryImageSource mis=new MemoryImageSource(64,144,new IndexColorModel(8,256,
				RGB[0],RGB[1],RGB[2],RGB[3]),noteBytes,0,64);
			showStatus("Creating note set "+i);
			for (j=0;j<6;j++)
				for (k=0;k<4;k++)
				{
					notePics[i][j*4+k]=createImage(new FilteredImageSource(mis,
						new CropImageFilter(k*16,j*24,16,24)));
					mt.addImage(notePics[i][j*4+k],2);
				}
		}
		showStatus("Waiting for completion");
		try
		{
			mt.waitForID(2);
		}
			catch(InterruptedException e) {}
		clefIm=createImage(46,142);
		clefG=clefIm.getGraphics();
		clefG.setColor(Color.white);
		clefG.fillRect(0,0,46,144);
		clefG.drawImage(clefs,0,22,this);
		clefG.setColor(Color.black);
		for (i=0;i<5;i++)
		{
			clefG.drawLine(32,28+7*i,46,28+7*i);
			clefG.drawLine(32,95+7*i,46,95+7*i);
		}
		offImage=createImage(382,142);
		offG=offImage.getGraphics();	
		offImage2=createImage(382,142);
		offG2=offImage2.getGraphics();
		strip=createImage(16,142);
		sG=strip.getGraphics();
		strip2=createImage(16,142);
		sG2=strip2.getGraphics();
	}

	public void drawPace()
	{
		if (barLength==6)
		{
			clefG.drawImage(paces[1],35,35,this);
			clefG.drawImage(paces[1],35,102,this);
		}
		else
		{
			clefG.drawImage(paces[0],35,35,this);
			clefG.drawImage(paces[0],35,102,this);
		}
	}

	public void drawNote(int x,int val,int length,int col,Graphics g)
	{
		if (lines[val]>0)
		{
			g.setColor(Color.black);
			g.drawLine(x+2,lines[val],x+14,
				lines[val]);
		}
		g.drawImage(notePics[col][flipOffset[val]+lengthVal[length]+noteHash[val]],
			x,noteH[val]+noteDH[val],this);
	}

	public void drawBar(int x,int barNum,Graphics g)
	{
		g.setColor(Color.white);
		g.fillRect(x,0,barLength*16+8,142);
		g.setColor(Color.black);
		g.drawLine(x+barLength*16+6,28,x+barLength*16+6,123);
		g.drawLine(x+barLength*16+7,28,x+barLength*16+7,123);
		for (i=0;i<5;i++)
		{
			g.drawLine(x,28+7*i,x+barLength*16+8,28+7*i);
			g.drawLine(x,95+7*i,x+barLength*16+8,95+7*i);
		}
		if ((barNum==lastBar)&&(barNum>0))
			g.fillRect(x+2,28,3,95);
		for (l=0;l<7;l++)
			for (i=0;i<barLength;i++)
				if (noteData[l][barNum*barLength+i]>0)
					drawNote(x+4+i*16+lengthOffset[noteData[l][barNum*barLength+i]],
						noteData[l+7][barNum*barLength+i],
						noteData[l][barNum*barLength+i],l,g);
		for (i=0;i<barLength;i++)
		{
			m=noteData[14][barNum*barLength+i];
			if ((m&7)>0)
				g.drawImage(pauses[(m&7)-1],x+8+i*16+pauseOffset[m&7],98,this);
			if ((m/8)>0)
				g.drawImage(pauses[(m/8)-1],x+8+i*16+pauseOffset[m/8],31,this);
		}
	}

	public void updateSheet()
	{
		if (scrollPos<6)
			offG.drawImage(clefIm,-scrollPos*8,0,this);
		j=(scrollPos+barLength*2+1-6)/(barLength*2+1)-1;
		k=j*8*(barLength*2+1)+46-8*scrollPos;
		while (j<0)
		{
			j++;
			k+=8*(barLength*2+1);
		}
		l=k;
		while (k<382)
		{
			drawBar(k,j,offG);
			j++;
			k+=8*(barLength*2+1);
		}
	}

	public void computePauses(int bar)
	{
		for (j=0;j<barLength;j++)
		{
			occupied[0][j]=0;
			occupied[1][j]=0;
			noteData[14][bar*barLength+j]=0;
		}
		for (j=0;j<7;j++)
			for (k=0;k<barLength;k++)
				if (noteData[j][bar*barLength+k]>0)
					if (noteData[j+7][bar*barLength+k]<36)
						for (l=0;l<noteData[j][bar*barLength+k];l++)
							occupied[0][k+l]=1;
					else
						for (l=0;l<noteData[j][bar*barLength+k];l++)
							occupied[1][k+l]=1;
		for (n=0;n<2;n++)
		{
			j=8;
			for (k=3;k>=0;k--)
			{
				m=0;
				for (l=barLength-1;l>=0;l--)
				{
					if (occupied[n][l]==0)
						m++;
					else
						m=0;
					freeRight[l]=m;
				}
				for (l=0;l<barLength;l+=j)
					if (freeRight[l]>=j)
					{
						noteData[14][bar*barLength+l]+=(1+n*7)*(k+1);
						for (m=l;m<l+j;m++)
							occupied[n][m]=1;
					}
				j=j/2;
			}
		}
	}

	public void fixAudioBytes()
	{
		i=32;
		m=32;
		j=1;
		l=1;
		n=255;
		o=0;
		for (k=0;k<8;k++)
		{
			while (l<m)
			{
				audioTable[8192+l]=(byte)(n-((l-o) >>> j));
				audioTable[8192-l]=(byte)(audioTable[8192+l]&0x7f);
				l++;
			}
			j++;
			n-=16;
			i*=2;
			o=m;
			m+=i;
		}
		audioTable[8192]=(byte)255;
	}

	public void buildAudioStream()
	{
		double fCount[]=new double[7],eCount[]=new double[7],step,goal,nLength,eStep;
		double d=0,t,fStep[]=new double[7];
		step=2.0e-6*speedBar.getValue();
		eStep=0.015;
		rawAudio=new byte[(int)(lastBar/step)+2];
		System.gc();
		nLength=1.0/barLength;
		goal=0;
		for (i=0;i<7;i++)
			for (j=0;j<64;j++)
				waveData[i][j]=waveforms[i][j]/30.0;
		sc=getScale();
		k=0;
		for (i=0;i<lastBar*barLength;i++)
		{
			for (j=0;j<7;j++)
			{
				lCount[j]--;
				if (noteData[j][i]>0)
				{
					lCount[j]=noteData[j][i];
					fStep[j]=frequencies[noteData[j+7][i]]*0.008;
					eCount[j]=0;
				}
			}
			goal+=nLength;
			while (d<goal)
			{
				t=0;
				for (j=0;j<7;j++)
					if (lCount[j]>0)
					{
						t+=waveData[j][(int)fCount[j]]*envelopes[j][(int)eCount[j]];
						fCount[j]+=fStep[j];
						if (fCount[j]>64.0)
							fCount[j]-=64.0;
						eCount[j]+=eStep;
						if (eCount[j]>63.0)
							eCount[j]=63.0;
					}
				rawAudio[k]=audioTable[8192+(int)(t*sc)];
				d+=step;
				k++;
			}
			if ((i%barLength)==0)
			  showStatus("Processing..."+(100*i/lastBar/barLength)+"%");
		}
	}

	public double getScale()
	{
		waveC.extractData();
		envC.extractData();
		for (i=0;i<7;i++)
		{
			k=0;
			for (j=0;j<64;j++)
				if (envelopes[i][j]>k)
					k=envelopes[i][j];
			maxVol[i]=k;
			lCount[i]=0;
		}
		l=0;
		for (i=0;i<lastBar*barLength;i++)
		{
			k=0;
			for (j=0;j<7;j++)
			{
				lCount[j]--;
				if (noteData[j][i]>0)
					lCount[j]=noteData[j][i];
				if (lCount[j]>0)
					k+=maxVol[j];
			}
			if (k>l)
				l=k;
		}
		if (l>8150)
			return 8150.0/l;
		else
			return 1.0;
	}

	public void saveNotes()
	{
		int exist[]=new int[7];
		waveC.extractData(); // Make sure the waveform and envelopes are up to date
		waveC.newData(waveforms[currCol],noteCol[currCol]);
		envC.extractData();
		envC.newData(envelopes[currCol],noteCol[currCol]);
		bufPos=0;
		sc=getScale();
		putFileByte(barLength);
		putFileByte(speedBar.getValue()-15);
		putFileByte(lastBar/64);
		putFileByte(lastBar%64);
		putFileByte(((int)(sc*4095))/64);
		putFileByte(((int)(sc*4095))%64);
		j=6;
		for (i=0;i<7;i++)
		{
			exist[i]=0;
			k=lastBar*barLength;
			for (l=0;l<k;l++)
				if (noteData[i][l]>0)
					l=k+1;
			if (l>k)
			{
				exist[i]=1;
				putFileByte(i);
			}
		}
		putFileByte(40); // Marks end
		outChars[64]='\0';
		while(bufPos<64)
			outChars[bufPos++]='\0';
		for (i=0;i<7;i++)
			if (exist[i]>0)
			{
				for (j=0;j<64;j++) // Save waveform and envelope
					putFileByte(30+waveforms[i][j]);
				for (j=0;j<64;j++)
				{
					putFileByte(envelopes[i][j]/64);
					putFileByte(envelopes[i][j]%64);
				}
				
				j=0; // Save note data
				k=0;
				l=0;
				while (j<(lastBar*barLength))
				{
					if (k>63)
					{
						putFileByte(72);
						putFileByte(64);
						k=0;
					}
					else if (noteData[i][j]==0)
					{
						j++;
						k++;
					}
					else
					{
						if (k>0)
						{
							putFileByte(72);
							putFileByte(k);
							k=0;
						}
						putFileByte(noteData[i+7][j]);
						putFileByte(noteData[i][j]);
						j+=noteData[i][j];
					}
				}
				if (k>0)
				{
						putFileByte(72);
						putFileByte(k);
				}
				while(bufPos<64)
					outChars[bufPos++]='\0';
			}
		System.out.println(outChars);
	}

	public void putFileByte(int c)
	{
		if (bufPos>63)
		{
			System.out.println(outChars);
			bufPos=0;
		}
		outChars[bufPos++]=(char)(32+c);
	}

	public void loadNotes(String s)
	{
		oldLength=barLength*lastBar;
		int exist[]=new int[7];
		bufPos=0;bufLength=0;
		try
		{
			URL levFile=new URL(getCodeBase(),s);
			URLConnection fileConn=levFile.openConnection();
			fileConn.connect();
			fileInput=fileConn.getInputStream();
		}
		catch (Exception ex)
		{}
		barLength=getFileByte();
		speedBar.setValue(getFileByte()+15);
		lastBar=64*getFileByte();
		lastBar+=getFileByte();
		j=getFileByte(); // Read past unnecessary data
		j=getFileByte();
		for (i=0;i<7;i++)
			exist[i]=0;
		j=getFileByte();
		while (j<8)
		{
			exist[j]=1;
			j=getFileByte();
		}
		for (i=0;i<7;i++) // Read each voice
		{
			for (j=0;j<(barLength*lastBar);j++) // Clear
				noteData[i][j]=0;
			if (exist[i]>0)
			{
				for (j=0;j<64;j++)
					waveforms[i][j]=getFileByte()-30;
				for (j=0;j<64;j++)
					envelopes[i][j]=64*getFileByte()+getFileByte();
				if (i==currCol)
				{
					waveC.newData(waveforms[currCol],noteCol[currCol]);
					envC.newData(envelopes[currCol],noteCol[currCol]);
				}
				j=0;
				while (j<(barLength*lastBar))
				{
					k=getFileByte();
					l=getFileByte();
					if (k<72) // Real note, not pause
					{
						noteData[i][j]=(byte)l;
						noteData[i+7][j]=(byte)k;
					}
					j+=l;
				}
			}
		}
		for (i=0;i<lastBar;i++)
			computePauses(i);
		if (oldLength>(lastBar*barLength))
			for (i=lastBar*barLength;i<oldLength;i++)
			{
				for (j=0;j<7;j++)
					noteData[j][i]=0;
				noteData[14][i]=0;
			}
	}

	public byte getFileByte()
	{
		btemp=0;
		while (btemp<32)
		{
			if (bufPos>=bufLength)
			{
				try
				{
					bufLength=fileInput.read(fileBuffer);
				}
				catch (Exception ex) {}
				bufPos=0;
			}
			btemp=fileBuffer[bufPos++];
		}
		return (byte)(btemp-32);
	}

	public boolean handleEvent(Event e)
	{
		scrollPos=bar.getValue();
		if ((e.target.equals(bar))&&(scrollPos!=oldScrollPos))
		{
			if ((e.id == Event.SCROLL_ABSOLUTE)||(e.id == Event.SCROLL_PAGE_UP)||
				(e.id == Event.SCROLL_PAGE_DOWN))
			{
				updateSheet();
			}
			else if (e.id == Event.SCROLL_LINE_DOWN)
			{
				offG2.drawImage(offImage,0,0,this);
				j=(scrollPos+barLength*2+1-6)/(barLength*2+1)-1;
				k=j*8*(barLength*2+1)+46-8*scrollPos;				
				while (k<382-8*(barLength*2+1))
				{
					j++;
					k+=8*(barLength*2+1);
				}
				offG.drawImage(offImage2,-8,0,this);
				drawBar(k,j,offG);
			}
			else if (e.id == Event.SCROLL_LINE_UP)
			{
				offG2.drawImage(offImage,0,0,this);
				if (scrollPos<6)
					offG.drawImage(clefIm,-scrollPos*8,0,this);
				j=(scrollPos+barLength*2+1-6)/(barLength*2+1)-1;
				k=j*8*(barLength*2+1)+46-8*scrollPos;				
				while (j<0)
				{
					j++;
					k+=8*(barLength*2+1);
				}
				offG.drawImage(offImage2,8,0,this);
				drawBar(k,j,offG);
			
			}
			repaint();
			oldScrollPos=scrollPos;
		}
		switch(e.id)
		{
			case Event.KEY_PRESS:
				checkKey(e.key,e.controlDown());
				break;
			case Event.MOUSE_MOVE:
				mx=e.x;
				my=e.y;
				break;
			case Event.MOUSE_DOWN: // Note selected?
				i=getBarNum();
				if (i>=0)
				{
					n=-1;
					m=100;
					j=getXOffset(i);
					for (l=6;l>=0;l--)
						for (k=0;k<barLength;k++)
							if (noteData[l][barLength*i+k]>0)
							{
								o=k*16+11+lengthOffset[noteData[l][barLength*i+k]]-j;
								o*=o;
								o+=(9+noteH[noteData[l+7][barLength*i+k]]-my)*
									(9+noteH[noteData[l+7][barLength*i+k]]-my);
								if ((o<100)&&(o<m))
								{
									m=o;
									n=k*8+l;
								}
							}
					if (n>=0)
					{
						selectedCol=n&7;
						selectedPos=barLength*i+(n/8);
						selectedLen=noteData[selectedCol][selectedPos];
						selectedVal=noteData[selectedCol+7][selectedPos];
						noteData[selectedCol][selectedPos]=0;
						noteData[selectedCol+7][selectedPos]=0;
						barStart=mx-j-9;
						stripX=9+barStart+4+16*(n/8)+lengthOffset[selectedLen];
						dx=stripX-Math.max(Math.min(stripX,375),9);
						drawBar(barStart-stripX+9+dx,i,sG);
						sG2.drawImage(strip,0,0,this);
						drawNote(dx,selectedVal,selectedLen,selectedCol,sG);
						drawStrip=true;
						stripX-=dx;
						repaint();
					}
				}
				break;
			case Event.MOUSE_DRAG: // Note dragged?
				if (selectedLen>0)
				{
					my=e.y;
					selectedVal=-1;
					j=25;
					for (i=0;i<72;i++)
					{
						k=9+noteH[i]-2*noteHash[i]-my;
						k*=k;
						if (k<j)
						{
							j=k;
							selectedVal=i;
						}
					}
					sG.drawImage(strip2,0,0,this);
					if (selectedVal>0)
						drawNote(dx,selectedVal,selectedLen,selectedCol,sG);
					drawStrip=true;
					repaint();
				}
				break;
			case Event.MOUSE_UP: // Note released?
				if (selectedLen>0)
				{
					drawStrip=false;
					if (selectedVal>=0)
					{
						noteData[selectedCol][selectedPos]=(byte)selectedLen;
						noteData[selectedCol+7][selectedPos]=(byte)selectedVal;
					}
					computePauses(selectedPos/barLength);
					drawBar(barStart,selectedPos/barLength,offG);
					selectedLen=0;
					repaint();
				}
				break;
			case Event.ACTION_EVENT:
			if (e.target instanceof Button)
			{
				for (i=0;i<7;i++)
					if (e.target.equals(noteB[i]))
					{
						currCol=i;
						waveC.extractData();
						waveC.newData(waveforms[i],noteCol[i]);
						envC.extractData();
						envC.newData(envelopes[i],noteCol[i]);
					}
				p=-1;
				for (i=0;i<9;i++)
					if (e.target.equals(controlB[i]))
						p=i;
				switch(p)
				{
					case 6: // Load file
						choiceHolder.show();
						validate();
						break;
					case 8: // "Save" file
						saveNotes();
						break;
					case 7: // Reset waveform & envelope
						for (i=0;i<64;i++)
						{
							waveforms[currCol][i]=waveCopy[currCol][i];
							envelopes[currCol][i]=envCopy[currCol][i];
						}
						waveC.newData(waveforms[currCol],noteCol[currCol]);
						envC.newData(envelopes[currCol],noteCol[currCol]);
						break;
					case 0: // Generate melody and play
						AudioPlayer.player.stop(soundStream);
						buildAudioStream();
						soundStream = new AudioDataStream(new AudioData(rawAudio));
						AudioPlayer.player.start(soundStream);
						break;
					case 1: // Play
						AudioPlayer.player.stop(soundStream);
						soundStream = new AudioDataStream(new AudioData(rawAudio));
						AudioPlayer.player.start(soundStream);
						break;
					case 2: // Stop
						AudioPlayer.player.stop(soundStream);
						break;
					case 5: // Clear
						for (i=0;i<barLength*lastBar;i++)
							for (j=0;j<15;j++)
								noteData[j][i]=0;
						lastBar=1;
						lastNote=-1;
						computePauses(0);
						updateSheet();
						repaint();
						break;
					case 3: // Switch to 3/4
						if ((lastNote<0)&&(lastBar==1))
						{
							barLength=6;
							drawPace();
							computePauses(0);
							updateSheet();
							repaint();
						}
						break;
					case 4: // Switch to 4/4
						if ((lastNote<0)&&(lastBar==1))
						{
							barLength=8;
							drawPace();
							computePauses(0);
							updateSheet();
							repaint();
						}
						break;
					default:
						break;
				}
			}
			else if (e.target instanceof Choice)
			{
				i=((Choice)e.target).getSelectedIndex();
				choiceHolder.hide();
				validate();
				loadNotes(trueInFiles.getItem(i));
				drawPace();
				updateSheet();
				repaint();
			}
			break;
		default:
			break;
		}
		return false;
	}
	
 	public void checkKey(int key, boolean CTRL)
	{
		for (i=0;i<7;i++)
			if ((key==((int)colNames.charAt(i)))||((key-32)==((int)colNames.charAt(i))))
			{
				currCol=i;
				waveC.extractData();
				waveC.newData(waveforms[i],noteCol[i]);
				envC.extractData();
				envC.newData(envelopes[i],noteCol[i]);
			}
		switch(key)
		{
			case 49:
			case 50:
			case 51:
			case 52:
			case 54:
			case 56:
				i=getBarNum();
				if (i>=0)
				{
					j=getXOffset(i);
					for (k=0;k<barLength;k++) // Clear counter
						freeRight[k]=0;
					for (k=0;k<barLength;k++) // Count available space
					{
						while ((noteData[currCol][barLength*i+k]>0)&&(k<barLength))
							k+=noteData[currCol][barLength*i+k];
						l=0;
						m=k;
						while ((m<barLength)&&(noteData[currCol][barLength*i+m]==0))
						{
							m++;
							l++;
						}
						freeRight[k]=l;
					}
					l=-1;
					m=100;
					for (k=0;k<barLength;k++) // Find closest match
					{
						n=k*16+11+lengthOffset[key&15]-j;
						n*=n;
						if ((n<100)&&(n<m)&&(freeRight[k]>=(key&15)))
						{
							l=k;
							m=n;
						}
					}
					if (l>=0)
					{
						n=100;
						for (k=0;k<72;k++) // Find closest note
						{
							o=9+noteH[k]-my;
							o*=o;
							if (o<n)
							{
								n=o;
								m=k;
							}
						}
						noteData[currCol][barLength*i+l]=(byte)(key&15);
						if (CTRL && (noteHash[m+1]>0)) // Hashed?
							m++;
						noteData[currCol+7][barLength*i+l]=(byte)m;
						lastNote=barLength*i+l;
						o=j;
						computePauses(i);
						if (i<lastBar)
							drawBar(mx-o-9,i,offG);
						else
						{
							lastBar=i+1;
							updateSheet();
						}
						repaint();
					}
				}
				break;
			case 8: // Delete
				i=getBarNum();
				if (i>=0)
				{
					n=-1;
					m=100;
					j=getXOffset(i);
					for (l=6;l>=0;l--)
						for (k=0;k<barLength;k++)
							if (noteData[l][barLength*i+k]>0)
							{
								o=k*16+11+lengthOffset[noteData[l][barLength*i+k]]-j;
								o*=o;
								o+=(9+noteH[noteData[l+7][barLength*i+k]]-my)*
									(9+noteH[noteData[l+7][barLength*i+k]]-my);
								if ((o<100)&&(o<m))
								{
									m=o;
									n=k*8+l;
								}
							}
					if (n>=0)
					{
						noteData[n&7][barLength*i+(n/8)]=0;
						noteData[n&7+7][barLength*i+(n/8)]=0;
						o=j;
						computePauses(i);
						drawBar(mx-o-9,i,offG);
						repaint();
					}
				}
				break;
			default:
				break;
		}
	}
 
	public int getBarNum()
	{
		if ((mx>=9)&&(mx<391)&&(my>=9)&&(my<155)&&(mx-46+scrollPos*8>=0))
			return (mx-9-46+scrollPos*8)/(barLength*16+8);
		else
			return -1;
	}

	public int getXOffset(int b)
	{
		return mx-9-46+scrollPos*8-b*(barLength*16+8);
	}

	public void paint(Graphics g)
	{
		g.drawImage(splash1,0,0,370,7,this);
		g.drawImage(splash2,0,7,7,100,this);
		g.setColor(Color.gray);
		g.draw3DRect(8,8,383,143,false);
		g.draw3DRect(7,7,385,145,false);
		g.setColor(Color.white);
		g.drawImage(offImage,9,9,this);
	}

	public void update(Graphics g)
	{
		if (drawStrip)
		{
			g.drawImage(strip,stripX,9,this);
			drawStrip=false;
		}
		else
			g.drawImage(offImage,9,9,this);
	}
	
}

class ActiveCanvas extends Canvas
{
	private Color c;
	private int graphData[],inData[],maxVal,minVal;
	private int i,j,k,l,m,startX=0,startY=0,ix1=0,ix2=0;
	private Dimension d;
	private boolean visualized=false,altered=false;
	private double a;
	
	ActiveCanvas(Color curve, int data[],int min,int max)
	{
		this.setBackground(Color.white);
		c=curve;
		inData=data;
		maxVal=max;
		minVal=min;
	}
	
	public void newData(int data[],Color curve)
	{
		altered=false;
		c=curve;
		inData=data;
		visualized=false;
		repaint();
	}
	
	public void extractData()
	{
		if (altered)
		{
			d=this.size();
			for (i=0;i<64;i++)
			{
				inData[i]=minVal+(maxVal-minVal)*(d.height-1-graphData[i*d.width/64])/d.height;
			}
		}
	}
	
	public boolean mouseDown(java.awt.Event evt, int x, int y)
	{
		startX=x;
		startY=y;
		return false;
	}
	
	public boolean mouseUp(java.awt.Event evt, int x, int y)
	{
		repaint();
		return false;
	}
	
	public boolean mouseDrag(java.awt.Event evt, int x, int y)
	{
		d=this.size();
		i=Math.max(Math.min(startX,x),0);
		j=Math.min(Math.max(startX,x),d.width-1);
		if (i!=j)
		{
			altered=true;
			if (x>startX)
			{
				l=startY;
				m=y;
			}
			else
			{
				l=y;
				m=startY;
			}
			l=Math.min(Math.max(0,l),d.height-1);
			m=Math.min(Math.max(0,m),d.height-1);
			for (k=i;k<=j;k++)
				graphData[k]=(int)(1.0*(m*(k-i)+l*(j-k))/(j-i));
			startX=x;
			startY=y;
			ix1=i;
			ix2=j+1;
			repaint();
		}
		return false;
	}

	private void prepareGraph()
	{
		d=this.size();
		graphData=new int[d.width];
		for (i=0;i<d.width;i++)
		{
			a=i*63.0/d.width-i*63/d.width;
			graphData[i]=d.height-1-(int)(d.height*((1-a)*inData[i*63/d.width]+
				a*inData[i*63/d.width+1]-minVal)/(maxVal-minVal));
		}
		visualized=true;
		ix1=0;
		ix2=d.width;
	}
	
	public void paint(Graphics g)
	{
		update(g);
	}
	
	public void update(Graphics g)
	{
		if (!visualized)
		{
			prepareGraph();
		}
		g.setColor(Color.white);
		d=this.size();
		g.fillRect(ix1,0,ix2-ix1,d.height);
		g.setColor(c);
		for (i=ix1;i<(ix2-1);i++)
			g.drawLine(i,graphData[i],i+1,graphData[i+1]);
		ix1=0;
		ix2=d.width;
	}
}
