import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import java.util.ArrayList;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.sound.sampled.*;


//**********************************************************************//
//                                CASound                               //
//**********************************************************************//
public class CASound extends JApplet
	implements Runnable
{
	private CAModel m_model;
	private CASoundView m_soundView;
        private int m_currentSoundView = 0;
	private Thread m_runner;
	private static final int m_TIMESTEP = 500;
        private boolean isPlaying = true;
        private boolean isMuted = false;
        private JButton startButton;
        private JButton muteButton;
        private CAOverheadView ohv;
        private CACutAwayView cav;
        private CAStrategy m_strategy;
        private JComboBox m_strategyCombo;
        private JComboBox m_seedCombo;
        private JFrame m_tweakFrame;
        private JTextField m_constantField;
        private JLabel m_constantLabel;
        private ImageIcon m_playIcon;
        private ImageIcon m_muteIcon;
        private float m_constant = 3.0F/2.0F;
        private static final String MULTIPLY_COMBO = "Multiply Constant";
        private static final String ADD_COMBO = "Add Constant";
        private static final String WAVE_COMBO = "Wave Equation";
        private static final String POINT_COMBO = "Point Seed";
        private static final String LINEAR_COMBO = "Linear Seed";
        private static final String SINE_COMBO = "Sine Wave Seed";
        public static final int NUM_CELLS = 200;
        public static final int NUM_CELL_HISTORY = 200;
        public static final int APPLET_WIDTH = 400;
        public static final int APPLET_HEIGHT = 400;
        
        private float[] linearSeed(int size, int numSteps)
        {
            int i;
            float dx = 1.0F / (float)numSteps;
            float val;
            int midpoint = (int)(size / 2.0F + 0.5F);
            float[] array = new float[size];
            for(i = 0; i < size; i++)
                array[i] = 0.0F;
            val = dx;    
            for(i = midpoint - numSteps + 1; i < midpoint; i++)
            {
                array[i] = val;
                val += dx;
            }
            val = 1.0F;
            for(; i < midpoint + numSteps; i++)
            {
                array[i] = val;
                val -= dx;
            }
            return array;
        }
        
        private float[] sineSeed(int size, int numNodes)
        {
            int i;
            double counter = 0.0;
            double dx = 2.0 * Math.PI / (size / numNodes);
            float[] array = new float[size];
            for(i = 0; i < size; i++)		//initialize
            {
                array[i] = (float)(Math.sin(counter) * 0.5 + 0.5);
                counter = (counter + dx) % 360.0;
            }
            return array;
        }
        
        private float[] pointSeed(int size)
        {
            int i;
            float[] array = new float[size];
            for(i = 0; i < size; i++)
                array[i] = 0.0F;
            array[(int)(size / 2.0F + 0.5F)] = 1.0F;
            return array;
        }

	public void init()
	{
		Container contentPane = getContentPane();
                contentPane.setLayout(new BorderLayout());
		setSize(APPLET_WIDTH, APPLET_HEIGHT);
		JPanel buttonPanel = new JPanel();
                m_playIcon = new ImageIcon(getClass().getResource("play.gif"));
                m_muteIcon = new ImageIcon(getClass().getResource("mute.gif"));
		startButton = new JButton("Stop");
                muteButton = new JButton(m_muteIcon);
		startButton.addActionListener(new
			ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
					if(!isPlaying)
                                        {
                                            start();
                                            startButton.setText("Stop");
                                            isPlaying = true;
                                        }
                                        else
                                        {
                                            stop();
                                            startButton.setText("Start");
                                            isPlaying = false;
                                        }
				}
			});
		muteButton.addActionListener(new
			ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
                                        if(isPlaying)
                                            m_soundView.mute(!m_soundView.getMute());
                                        isMuted = !isMuted;
                                        if(isMuted)
                                            muteButton.setIcon(m_playIcon);
                                        else
                                            muteButton.setIcon(m_muteIcon);
				}
			});
                m_strategyCombo = new JComboBox();
                m_strategyCombo.addItem(MULTIPLY_COMBO);
                m_strategyCombo.addItem(ADD_COMBO);
                m_strategyCombo.addItem(WAVE_COMBO);
                m_strategyCombo.setMaximumSize(m_strategyCombo.getPreferredSize());
                m_strategyCombo.addActionListener(new
                    ActionListener()
                    {
                        public void actionPerformed(ActionEvent event)
                        {
                            String item = (String)m_strategyCombo.getSelectedItem();
                            if(item.equals(MULTIPLY_COMBO))
                            {
                                if(isPlaying)
                                    stop();
                                m_strategy = new CAMultiplyConstantStrategy(m_constant);
                                m_model.setBehaviorAlgorithm(m_strategy);
                                if(isPlaying)
                                    start();
                            }
                            else if(item.equals(ADD_COMBO))
                            {
                                if(isPlaying)
                                    stop();
                                m_strategy = new CAAddConstantStrategy(m_constant);
                                 m_model.setBehaviorAlgorithm(m_strategy);
                                if(isPlaying)
                                    start();
                            }
                            else if(item.equals(WAVE_COMBO))
                            {
                                if(isPlaying)
                                    stop();
                                m_strategy = new CAWaveStrategy();
                                 m_model.setBehaviorAlgorithm(m_strategy);
                                if(isPlaying)
                                    start();
                            }
                        }
                    });

                m_seedCombo = new JComboBox();
                m_seedCombo.addItem(POINT_COMBO);
                m_seedCombo.addItem(LINEAR_COMBO);
                m_seedCombo.addItem(SINE_COMBO);
                m_seedCombo.setMaximumSize(m_seedCombo.getPreferredSize());               
                m_seedCombo.addActionListener(new
                                                  ActionListener()
                                                  {
                                                      public void actionPerformed(ActionEvent event)
                                                  {
                                                          String item = (String)m_seedCombo.getSelectedItem();
                                                          if(item.equals(POINT_COMBO))
                                                              m_model.setSeed(pointSeed(NUM_CELLS));
                                                          else if(item.equals(LINEAR_COMBO))
                                                              m_model.setSeed(linearSeed(NUM_CELLS, NUM_CELLS / 10));
                                                          else if(item.equals(SINE_COMBO))
                                                              m_model.setSeed(sineSeed(NUM_CELLS, 5));
                                                  }
                                                  });
                
                m_tweakFrame = new JFrame();
                m_tweakFrame.setTitle("Tweak CASound");
                m_tweakFrame.setSize(280, 150);
                m_tweakFrame.setResizable(false);
                Container tweakContentPane = m_tweakFrame.getContentPane();
                JButton tweakButton = new JButton("Tweak");
                contentPane.add(tweakButton);
                tweakButton.addActionListener(new
                    ActionListener()
                    {
                        public void actionPerformed(ActionEvent evt)
                        {
                            if(m_tweakFrame.isVisible())
                                m_tweakFrame.setVisible(false);
                            else
                                m_tweakFrame.show();
                        }
                    });
                m_constantField = new JTextField("" + m_constant, 7);
                m_constantField.setMaximumSize(m_constantField.getPreferredSize());
                m_constantField.addKeyListener(new 
                    KeyAdapter()
                    {
                        public void keyPressed(KeyEvent e)
                        {
                            if(e.getKeyCode() == e.VK_ENTER)
                            {
                                try
                                {
                                    float constant = Float.parseFloat(m_constantField.getText().trim());
                                    //set the constant
                                    m_constant = constant;
                                    if(isPlaying)
                                        stop();
                                    m_strategy.setConstant(m_constant);
                                    m_model.setBehaviorAlgorithm(m_strategy);
                                    if(isPlaying)
                                        start();
                                    m_constantField.setText("" + m_constant);
                                    m_constantLabel.setText("Constant: " + m_constant);
                                }
                                catch(NumberFormatException event)
                                {
                                    JOptionPane.showMessageDialog(m_tweakFrame,
                                                    m_constantField.getText().trim()
                                                    + " is not a number.",
                                                    "Error!",
                                                    JOptionPane.ERROR_MESSAGE);
                                    m_constantField.setText("" + m_constant);
                                }
                            }                        
                        }
                    });
                
                m_constantLabel = new JLabel("Constant: " + m_constant);
                JButton constantEdit = new JButton("Enter");
                constantEdit.addActionListener(new
                    ActionListener()
                    {
                        public void actionPerformed(ActionEvent evt)
                        {
                            try
                            {
                                float constant = Float.parseFloat(m_constantField.getText().trim());
                                //set the constant
                                m_constant = constant;
                                if(isPlaying)
                                    stop();
                                m_strategy.setConstant(m_constant);
                                m_model.setBehaviorAlgorithm(m_strategy);
                                if(isPlaying)
                                    start();
                                m_constantField.setText("" + m_constant);
                                m_constantLabel.setText("Constant: " + m_constant);
                            }
                            catch(NumberFormatException e)
                            {
                                JOptionPane.showMessageDialog(m_tweakFrame,
                                                m_constantField.getText().trim()
                                                + " is not a number.",
                                                "Error!",
                                                JOptionPane.ERROR_MESSAGE);
                                m_constantField.setText("" + m_constant);
                            }
                        }
                    });
                    
                JButton seedButton = new JButton ("Seed");
                seedButton.addActionListener(new
                    ActionListener()
                    {
                        public void actionPerformed(ActionEvent evt)
                        {
                            if(isPlaying)
                                stop();
                            m_model.reset();
                            if(isPlaying)
                                start();
                        }
                    });
                
                //setup the Tweak CASound panel    
                Box hbox = Box.createHorizontalBox();
                Box vbox = Box.createVerticalBox();
                hbox.add(m_constantLabel);
                hbox.add(Box.createGlue());
                vbox.add(hbox);
                
                hbox = Box.createHorizontalBox();
                hbox.add(new JLabel("Constant:"));
                hbox.add(m_constantField);
                hbox.add(constantEdit);
                hbox.add(Box.createGlue());
                vbox.add(hbox);
                
                hbox = Box.createHorizontalBox();
                hbox.add(new JLabel("Strategy:"));
                hbox.add(m_strategyCombo);
                hbox.add(Box.createGlue());
                vbox.add(hbox);
                
                hbox = Box.createHorizontalBox();
                hbox.add(new JLabel("Seed:     "));
                hbox.add(m_seedCombo);
                hbox.add(seedButton);
                hbox.add(Box.createGlue());
                vbox.add(hbox);
                
                tweakContentPane.add(vbox);
		
                buttonPanel.add(startButton);
                buttonPanel.add(tweakButton);
                buttonPanel.add(muteButton);
                
                //seed the model
                float[] newState = pointSeed(NUM_CELLS);


		// setup the cell model
                m_strategy = new CAMultiplyConstantStrategy(m_constant);
                               
		m_model = new CAModel(NUM_CELLS, NUM_CELL_HISTORY,
                                            m_strategy, newState);
		
                cav = new CACutAwayView(m_model);
		ohv = new CAOverheadView(m_model);
                
                m_soundView = new CASoundView(m_model, NUM_CELLS);
                m_soundView.mute(true);
                m_soundView.start();
                
                cav.setMinimumSize(new Dimension(NUM_CELLS, APPLET_HEIGHT / 8));
                cav.setPreferredSize(new Dimension(NUM_CELLS, APPLET_HEIGHT / 8));
                contentPane.add(cav, BorderLayout.NORTH);
		contentPane.add(ohv, BorderLayout.CENTER);
		contentPane.add(buttonPanel, BorderLayout.SOUTH);
	}

	/*
	Implemented method from Runnable.
	*/
	public void start()
	{
		if(m_runner == null)
		{
			m_runner = new Thread(this);
			m_runner.start();
			showStatus("CASound is now running...");
		}
	}

	public void stop()
	{
		if(m_runner != null)
		{
			m_soundView.mute(true);
			m_runner.interrupt();
			m_runner = null;
                        System.gc();	//request garbage collection
		}
		showStatus("CASound is now stopped.");
	}

	public void run()
	{
		long startTime;
		try
		{
			startTime = System.currentTimeMillis();
			while(!Thread.interrupted())
			{                            
				if(!isMuted)
                                {
                                    m_soundView.mute(true);;
                                    m_model.behave();
                                    m_soundView.mute(false);
                                }
                                else
                                    m_model.behave();
                                ohv.animate();
                                cav.animate();
                                repaint();
				startTime += m_TIMESTEP;
				Thread.sleep(Math.max(0, startTime - System.currentTimeMillis()));
				startTime = System.currentTimeMillis();
			}
		}
		catch(InterruptedException e) {}
	}
}

//**********************************************************************//
//                                CAModel                               //
//**********************************************************************//
class CAModel
{
	private int m_numCells;
	private int m_maxBufferSize;
	private ArrayList m_buffer;
	private CAStrategy m_strategy;
        private float[] m_seed;

	public CAModel(int numCells, int maxBufferSize, CAStrategy strategy, float[] seed)
	{
		m_numCells = numCells;
		m_maxBufferSize = maxBufferSize;
		m_buffer = new ArrayList();
		m_strategy = strategy;
                m_seed = seed;
	}

	//getters

	public final int getNumCells() { return m_numCells; }

	public final int getMaxBufferSize() { return m_maxBufferSize; }

	public final ArrayList getBuffer() { return m_buffer; }

	public final synchronized float[] getCurrentState(boolean setState, float[] newState)
	{
		if(setState)
                {
                    m_buffer.add(newState);
                    if(m_buffer.size() == m_maxBufferSize)
                        m_buffer.remove(0);
                }
                if(m_buffer.isEmpty())
                    return null;
                else
                    return (float[])m_buffer.get(m_buffer.size() - 1);
	}

	//setters

	//throw an error if the newState is not the correct size or null
	public void setNewState(float[] newState)
	{
		m_buffer.add(newState);
		if(m_buffer.size() == m_maxBufferSize)
			m_buffer.remove(0);
	}

	//check if newSize > 0
	public void setMaxBufferSize(int newSize)
	{
		m_maxBufferSize = newSize;
		while(m_buffer.size() > m_maxBufferSize)
			m_buffer.remove(0);
	}

	public void setNumCells(int numCells) { m_numCells = numCells; }

	public void reset() { m_buffer.clear(); }

        public void setSeed(float[] seed) { m_seed = seed; } 

	public void setBehaviorAlgorithm(CAStrategy behavior) { m_strategy = behavior; }

	public void behave()
	{
		if(!m_buffer.isEmpty())
                    m_strategy.behaveAlgorithm(this);
                else
                {
                    this.getCurrentState(true, m_seed);                
                }
	}

}
//**********************************************************************//
//                                CAStategy                             //
//**********************************************************************//
interface CAStrategy
{
	public void behaveAlgorithm(CAModel caller);
        public float getConstant();
        public void setConstant(float constant);
}
//**********************************************************************//
//                                CAMultiplyConstantStategy             //
//**********************************************************************//
class CAMultiplyConstantStrategy implements CAStrategy
{
	private float m_constant;

	public CAMultiplyConstantStrategy(float constant)
	{
		m_constant = constant;
	}

	public final float getConstant() { return m_constant; }

	public void setConstant(float constant) { m_constant = constant; }

	public void behaveAlgorithm(CAModel caller)
	{
		float[] currentState = caller.getCurrentState(false, null);
		if(currentState == null || currentState.length == 0)
			return;
		else
		{
			float sum;
			int i;
			int size = currentState.length;
			int numNeighbors = 1;	//includes self
			float[] newState = new float[size];

			//handle the first cell
			sum = currentState[0];
			if(size > 1)
			{
				sum += currentState[size - 1];
				numNeighbors++;
			}
			if(size > 2)
			{
				sum	+= currentState[1];
				numNeighbors++;
			}
			newState[0] = (m_constant * (sum / numNeighbors)) % 1.0F;

			//handle the middle cells
			for(i = 1; i < (size - 1); i++)
			{
				sum = currentState[i]
						+ currentState[i - 1]
                                                + currentState[i + 1];
				newState[i] = (m_constant * (sum / 3)) % 1.0F;
			}

			//handle last cell
			if(size > 1)
			{
				numNeighbors = 2;
				sum = currentState[size - 1]
						+ currentState[0];
				if(size > 2)
				{
					numNeighbors++;
					sum += currentState[size - 2];
				}

				newState[size - 1] = (m_constant * (sum / numNeighbors)) % 1.0F;
			}

			caller.getCurrentState(true, newState);
		}
	}
}
//**********************************************************************//
//                                CAAddConstantStategy                  //
//**********************************************************************//
class CAAddConstantStrategy implements CAStrategy
{
	private float m_constant;

	public CAAddConstantStrategy(float constant)
	{
		m_constant = constant;
	}

	public final float getConstant() { return m_constant; }

	public void setConstant(float constant) { m_constant = constant; }

	public void behaveAlgorithm(CAModel caller)
	{
		float[] currentState = caller.getCurrentState(false, null);
		if(currentState == null || currentState.length == 0)
			return;
		else
		{
			float sum;
			int i;
			int size = currentState.length;
			int numNeighbors = 1;	//includes self
			float[] newState = new float[size];

			//handle the first cell
			sum = currentState[0];
			if(size > 1)
			{
				sum += currentState[size - 1];
				numNeighbors++;
			}
			if(size > 2)
			{
				sum	+= currentState[1];
				numNeighbors++;
			}
			newState[0] = (m_constant + (sum / numNeighbors)) % 1.0F;

			//handle the middle cells
			for(i = 1; i < (size - 1); i++)
			{
				sum = currentState[i]
						+ currentState[i - 1]
                                                + currentState[i + 1];
				newState[i] = (m_constant + (sum / 3)) % 1.0F;
			}

			//handle last cell
			if(size > 1)
			{
				numNeighbors = 2;
				sum = currentState[size - 1]
						+ currentState[0];
				if(size > 2)
				{
					numNeighbors++;
					sum += currentState[size - 2];
				}

				newState[size - 1] = (m_constant + (sum / numNeighbors)) % 1.0F;
			}

			caller.getCurrentState(true, newState);
		}
	}
}
//**********************************************************************//
//                                CAWaveStrategy                        //
//**********************************************************************//
class CAWaveStrategy implements CAStrategy
{       
	private float m_constant = 0.0F;
        
        public final float getConstant() { return m_constant; }

	public void setConstant(float constant) { m_constant = constant; }
        
        public void behaveAlgorithm(CAModel caller)
	{
		float[] currentState = caller.getCurrentState(false, null);
		if(currentState == null || currentState.length == 0)
			return;
		else
		{
			float[] previousState;
                        ArrayList buffer = caller.getBuffer();
                        float sum;
			int i;
			int size = currentState.length;
			float[] newState = new float[size];
                        
                        if(buffer.size() < 2)
                        {
                            previousState = currentState;
                        }
                        else
                            previousState = (float[])buffer.get(buffer.size() - 2);

			//handle the first cell
			sum = - previousState[0];
			if(size > 1)
                            sum += currentState[size - 1];

			if(size > 2)
                            sum	+= currentState[1];

			newState[0] = sum;

			//handle the middle cells
			for(i = 1; i < (size - 1); i++)
                        {
                            sum = currentState[i - 1]
                                  + currentState[i + 1]
                                  - previousState[i];
                            newState[i] = sum;
                        }

			//handle last cell
			if(size > 1)
			{
				sum = currentState[0] - previousState[size - 1];
				if(size > 2)
                                    sum += currentState[size - 2];

				newState[size - 1] = sum;
			}
			caller.getCurrentState(true, newState);
		}
	}
}
//**********************************************************************//
//                                CAView                                //
//**********************************************************************//
abstract class CAView extends JPanel
{
	protected CAModel m_model;
        protected BufferedImage m_doubleBuffer = null;
        protected Graphics2D g2 = null;
	public CAView(CAModel model) { m_model = model;}
        public abstract void animate();
}
//**********************************************************************//
//                                CAOverheadView                        //
//**********************************************************************//
class CAOverheadView extends CAView
{
	private boolean firstDraw = true;
        
        
        public CAOverheadView(CAModel model)
	{
		super(model);
	}
        
        public void animate()
        {
		Dimension size = this.getSize ();
                if(m_doubleBuffer == null)
                {
                    m_doubleBuffer = new BufferedImage (size.width, size.height, BufferedImage.TYPE_INT_RGB);
                    g2 = (Graphics2D) m_doubleBuffer.getGraphics ();
                }
		float width, height;
		width = (float)size.getWidth();
		height = (float)size.getHeight();
		
                int i;
		int rowSize;
		float tempVal;
		float[] currentState = m_model.getCurrentState(false, null);

		rowSize = m_model.getNumCells();
		int rectangleWidth = (int)(width / rowSize + 0.5F);
		int rectangleHeight = (int)(height / m_model.getMaxBufferSize() + 0.5F);
                if(!firstDraw)
                    g2.copyArea(0, 0, (int)width, (int)(height - rectangleHeight), 0, rectangleHeight);
                else
                {
                    g2.setPaint(Color.white);
                    g2.fillRect(0, 0, (int)width, (int)height);
                    firstDraw = false;
                }
		for(i = 0; i < rowSize; i++)
		{
			tempVal = 1.0F - currentState[i];
                        if(tempVal < 0.0F)		//clamp color values: 0.0 <-> 1.0
                            tempVal = 0.0F;
                        else if(tempVal > 1.0F)
                            tempVal = 1.0F;
			g2.setPaint(new Color(tempVal, tempVal, tempVal));
			g2.fillRect(i * rectangleWidth, 0, rectangleWidth, rectangleHeight);
		}        
        }

	public void paintComponent(Graphics g)
	{
		super.paintComponent(g);
                if(m_doubleBuffer != null)
                    g.drawImage (m_doubleBuffer, 0, 0, this);             
	}
}
//**********************************************************************//
//                                CACutAwayView                         //
//**********************************************************************//
class CACutAwayView extends CAView
{

	public CACutAwayView(CAModel model)
	{
		super(model);
	}
        
        public void animate()
        {
            Dimension size = this.getSize ();
            if(m_doubleBuffer == null)
            {
                m_doubleBuffer = new BufferedImage (size.width, size.height, BufferedImage.TYPE_INT_RGB);
                g2 = (Graphics2D) m_doubleBuffer.getGraphics ();
                g2.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING,
                                                            RenderingHints.VALUE_ANTIALIAS_ON));
            }
            g2.setPaint(Color.black);
            g2.fillRect(0, 0, (int)getSize().getWidth(), (int)getSize().getHeight());
            g2.setPaint(Color.white);

            int i;
            int rowSize;
            float height = (float)getSize().getHeight();
            float x_Offset;
            float tempVal;
            float[] currentState = m_model.getCurrentState(false, null);
            if(currentState != null)
            {
                rowSize = currentState.length;
                if(rowSize > 0)
                {
                    Line2D.Float line = new Line2D.Float();
                    tempVal = 1.0F - currentState[0];
                    Point2D.Float prevPoint = new Point2D.Float(0.0F, tempVal * height);
                    x_Offset = (float)getSize().getWidth() / rowSize;
                    for(i = 1; i < rowSize; i++)
                    {
                        tempVal = 1.0F - currentState[i];
                        line.setLine(prevPoint.getX(), prevPoint.getY(), i * x_Offset, tempVal * height);
                        g2.draw(line);
                        prevPoint.setLocation(i * x_Offset, tempVal * height);
                    }
                }
            }
        }

	public void paintComponent(Graphics g)
	{
		super.paintComponent(g);
                if(m_doubleBuffer != null)
                    g.drawImage (m_doubleBuffer, 0, 0, this);
	}
}
//**********************************************************************//
//                                CASoundView                           //
//NOTES: This class was inspired by the JASS API.			//
//		http://www.cs.ubc.ca/spider/kvdoel/jass/		//
//**********************************************************************//
class CASoundView extends Thread
{
	protected CAModel m_model;
	protected int m_bufferSize;
        public static final float SAMPLING_RATE = 44100.0F;
        private boolean isMuted = false;
        private int numScratchBuffers;
        private byte byteBuffer[];
        private SourceDataLine sourceDataLine;
        private Mixer mixer;
        private AudioFormat audioFormat;

        final private void floatToByte(byte[] byteSound, float [] dbuf) {
            int ib=0;
            for(int i=0; i<m_bufferSize; i++)
            {
                short y = (short)(32767. * dbuf[i]);
                byteSound[ib] = (byte)(y & 0x00ff);
                ib++;
                byteSound[ib] = (byte)(y >> 8);
                ib++;
            }
        }

        private void initAudio(float f, int j, int k)
        {
            findMixer();
            audioFormat = new AudioFormat(f, j, k, true, false);
            javax.sound.sampled.DataLine.Info info;

            info = new javax.sound.sampled.DataLine.Info(javax.sound.sampled.SourceDataLine.class, audioFormat);

            if(!mixer.isLineSupported(info))
                System.out.println(getClass().getName()
                            + " : Error: sourcedataline: not supported (this may be a bogus message under Tritonus)\n");
            try
            {
                sourceDataLine = (SourceDataLine)mixer.getLine(info);
                sourceDataLine.open(audioFormat);
                sourceDataLine.start();
            }
            catch(LineUnavailableException lineunavailableexception)
            {
                System.out.println(getClass().getName() + " : Error getting line\n");
            }
        }

        private void findMixer()
        {
            javax.sound.sampled.Mixer.Info ainfo[] = AudioSystem.getMixerInfo();
            System.out.println("CHOSEN MIXER: " + ainfo[0].getName());
            mixer = AudioSystem.getMixer(ainfo[0]);
        }
        
        
	public CASoundView(CAModel model, int bufferSize) 
       { 
            m_model = model;
            m_bufferSize = bufferSize;
            numScratchBuffers = 2 * m_bufferSize;
            byteBuffer = new byte[numScratchBuffers];
            setPriority(java.lang.Thread.MAX_PRIORITY);
            initAudio(SAMPLING_RATE, 16, 1);
       }

	public final void mute(boolean mute) 
        {
            isMuted = mute;
            if(mute)
            {
                sourceDataLine.stop();
                sourceDataLine.flush();
            }
            else
            {
                sourceDataLine.start();
            }
        }
        public final boolean getMute()
        {
            return isMuted;
        }
      
        public void run()
        {
            while(!interrupted())
            {
                   while(!isMuted)
                   {
                       floatToByte(byteBuffer, m_model.getCurrentState(false, null));
                       if(!isMuted)
                           sourceDataLine.write(byteBuffer, 0, numScratchBuffers);
                   }
            }
            close();
        }

        public void close()
        {
            sourceDataLine.stop();
            sourceDataLine.close();
            mixer.close();
        }
}
