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> 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(); } }