Coverage Report - uk.co.javagear.SN76496
 
Classes in this File Line Coverage Branch Coverage Complexity
SN76496
0% 
0% 
3.571
 
 1  
 /*
 2  
  * SN76496.java
 3  
  *
 4  
  * This file is part of JavaGear.
 5  
  *
 6  
  * JavaGear is free software; you can redistribute it and/or modify
 7  
  * it under the terms of the GNU General Public License as published by
 8  
  * the Free Software Foundation; either version 2 of the License, or
 9  
  * (at your option) any later version.
 10  
  *
 11  
  * JavaGear is distributed in the hope that it will be useful,
 12  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  
  * GNU General Public License for more details.
 15  
  *
 16  
  * You should have received a copy of the GNU General Public License
 17  
  * along with JavaGear; if not, write to the Free Software
 18  
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 19  
  */
 20  
 
 21  
 package uk.co.javagear;
 22  
 
 23  
 import java.io.File;
 24  
 import java.io.FileInputStream;
 25  
 import java.io.FileWriter;
 26  
 import java.io.IOException;
 27  
 import javax.sound.sampled.AudioFileFormat;
 28  
 import javax.sound.sampled.AudioFormat;
 29  
 import javax.sound.sampled.AudioInputStream;
 30  
 import javax.sound.sampled.AudioSystem;
 31  
 import javax.sound.sampled.DataLine;
 32  
 import javax.sound.sampled.LineUnavailableException;
 33  
 import javax.sound.sampled.SourceDataLine;
 34  
 import org.apache.log4j.Logger;
 35  
 
 36  
 /**
 37  
  * Texas SN76496 Emulation.
 38  
  *
 39  
  * @author Copyright (C) 2002-2003 Chris White
 40  
  * @version 18th January 2003
 41  
  * @see "JavaGear Final Project Report"
 42  
  */
 43  
 public final class SN76496 {
 44  
     /**
 45  
      * Tone Generator 1.
 46  
      */
 47  
     private ToneGenerator chan0;
 48  
     
 49  
     /**
 50  
      * Tone Generator 2.
 51  
      */
 52  
     private ToneGenerator chan1;
 53  
     
 54  
     /**
 55  
      * Tone Generator 3.
 56  
      */
 57  
     private ToneGenerator chan2;
 58  
     
 59  
     /**
 60  
      * Noise Generator.
 61  
      */
 62  
     private NoiseGenerator chan3;
 63  
     
 64  
     /**
 65  
      * Pointer to current Tone Generator.
 66  
      */
 67  
     private ToneGenerator currentGenerator;
 68  
     
 69  
     /**
 70  
      * Buffer for sound data.
 71  
      */
 72  
     private byte [] buffer;
 73  
     
 74  
     /**
 75  
      * For Recording Sound to Disk.
 76  
      */
 77  
     private FileWriter fileWriter;
 78  
     
 79  
     /**
 80  
      * Specifies the arrangement of sound data.
 81  
      */
 82  
     private AudioFormat audioFormat;
 83  
     
 84  
     /**
 85  
      * The line that transmits audio to speakers.
 86  
      */
 87  
     private SourceDataLine line;
 88  
     
 89  
     /**
 90  
      * PSG Clock Speed.
 91  
      */
 92  
     private double clockSpeed;
 93  
     
 94  
     /**
 95  
      * Output Sample Rate.
 96  
      */
 97  
     private int sampleRate;
 98  
     
 99  
     /**
 100  
      * Samples to Generate per video frame.
 101  
      */
 102  
     private int samplesPerFrame;
 103  
     
 104  
     /**
 105  
      * Sound Enabled.
 106  
      */
 107  
     private boolean enabled;
 108  
     
 109  
     /**
 110  
      * Record Sound to Disk.
 111  
      */
 112  
     private boolean recording;
 113  
     
 114  
     
 115  
     
 116  
     /**
 117  
      * SN76496 Constructor.
 118  
      *
 119  
      * @param c Clock Speed (Hz)
 120  
      * @param s Sample Rate (Hz)
 121  
      */
 122  0
     public SN76496(double c, int s) {
 123  0
         clockSpeed = c;
 124  0
         sampleRate = s;
 125  
         
 126  0
         chan0 = new ToneGenerator(clockSpeed, sampleRate);
 127  0
         chan1 = new ToneGenerator(clockSpeed, sampleRate);
 128  0
         chan2 = new ToneGenerator(clockSpeed, sampleRate);
 129  0
         chan3 = new NoiseGenerator(clockSpeed, sampleRate, chan2);
 130  
         
 131  
         // AudioFormat(sample_rate(hz), sampleSizeInBits, channels, signed, bigEndian)
 132  0
         audioFormat = new AudioFormat(sampleRate, 8, 1, true, false);
 133  0
         DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
 134  
         
 135  0
         if (!AudioSystem.isLineSupported(info)) {
 136  0
             System.out.print("Audio not supported...");
 137  
         } else {
 138  
             try {
 139  0
                 line = (SourceDataLine) AudioSystem.getLine(info);
 140  0
                 line.open(audioFormat);
 141  0
                 line.start();
 142  0
             } catch (LineUnavailableException lue) {
 143  0
                 Logger.getLogger(this.getClass()).error("Unable to open audio line.", lue);
 144  0
                 System.out.print("Audio Line Unavailable...");
 145  0
             }
 146  
         }
 147  
         
 148  0
         recording = false;
 149  
         
 150  
         // Default to 60 FPS
 151  0
         setFPS(60);
 152  
         
 153  
         // Reset to Defaults
 154  0
         reset();
 155  
         
 156  
         // Enabled by Default
 157  0
         setEnabled();
 158  0
     }
 159  
     
 160  
     
 161  
     /**
 162  
      * Reset SN76496 to Default Values.
 163  
      */
 164  
     public void reset() {
 165  0
         currentGenerator = chan0;
 166  0
         chan0.reset();
 167  0
         chan1.reset();
 168  0
         chan2.reset();
 169  0
         chan3.reset();
 170  0
     }
 171  
     
 172  
     
 173  
     /**
 174  
      * Returns <code>true</code> if this instance of <code>SN76496</code> is enabled and
 175  
      * <code>false</code> otherwise.
 176  
      *
 177  
      * @return <code>true</code> if enabled.
 178  
      */
 179  
     public boolean isEnabled() {
 180  0
         return enabled;
 181  
     }
 182  
     
 183  
     
 184  
     /**
 185  
      * Toggle SN76496 On/Off.
 186  
      */
 187  
     public void setEnabled() {
 188  0
         if (enabled) {
 189  0
             stopSound();
 190  
         } else {
 191  0
             startSound();
 192  
         }
 193  0
     }
 194  
     
 195  
     
 196  
     /**
 197  
      * Toggle a particular channel On/Off.
 198  
      *
 199  
      * @param channel channel to toggle (0-3)
 200  
      * @throws IllegalArgumentException if the value of channels is not 1, 2 or 3.
 201  
      */
 202  
     public void setChannelEnabled(int channel) throws IllegalArgumentException {
 203  0
         switch (channel) {
 204  
             case 0:
 205  0
                 chan0.setEnabled();
 206  0
                 break;
 207  
             case 1:
 208  0
                 chan1.setEnabled();
 209  0
                 break;
 210  
             case 2:
 211  0
                 chan2.setEnabled();
 212  0
                 break;
 213  
             case 3:
 214  0
                 chan3.setEnabled();
 215  0
                 break;
 216  
             default:
 217  0
                 throw new IllegalArgumentException("Invalid Channel: " + channel);
 218  
         }
 219  0
     }
 220  
     
 221  
     /**
 222  
      * Set current FPS Rate.
 223  
      * Required to generate the correct number of samples per frame.
 224  
      *
 225  
      * @param v FPS Rate
 226  
      */
 227  
     public void setFPS(int v) {
 228  0
         samplesPerFrame = (sampleRate / v);
 229  0
         buffer = new byte[samplesPerFrame];
 230  
         
 231  0
     }
 232  
     
 233  
     
 234  
     /**
 235  
      * Program the PSG. Connected this procedure to a Z80 Port.
 236  
      *
 237  
      * @param value Value to write (0-0xFF)
 238  
      */
 239  
     public void write(int value) {
 240  0
         if ((!enabled) && (!recording)) {
 241  0
             return;
 242  
         }
 243  
         
 244  0
         if ((value & 0x80) == 0x80) {
 245  0
             switch((value >> 4) & 0x07) {
 246  
                 case 0x00: // 000 rr = 00 (Channel 0 Frequency)
 247  0
                     chan0.setFirstByte(value & 0x0F);
 248  0
                     currentGenerator = chan0;
 249  0
                     break;
 250  
                 case 0x01: // 001 rr = 00 (Channel 0 Volume)
 251  0
                     chan0.setVolume(value & 0x0F);
 252  0
                     currentGenerator = chan0;
 253  0
                     break;
 254  
                 case 0x02: // 010 rr = 01 (Channel 1 Frequency)
 255  0
                     chan1.setFirstByte(value & 0x0F);
 256  0
                     currentGenerator = chan1;
 257  0
                     break;
 258  
                 case 0x03: // 011 rr = 01 (Channel 1 Volume)
 259  0
                     chan1.setVolume(value & 0x0F);
 260  0
                     currentGenerator = chan1;
 261  0
                     break;
 262  
                 case 0x04: // 100 rr = 10 (Channel 2 Frequency)
 263  0
                     chan2.setFirstByte(value & 0x0F);
 264  0
                     currentGenerator = chan2;
 265  0
                     break;
 266  
                 case 0x05: // 101 rr = 10 (Channel 2 Volume)
 267  0
                     chan2.setVolume(value & 0x0F);
 268  0
                     currentGenerator = chan2;
 269  0
                     break;
 270  
                 case 0x06: // 110 rr = 11 (Noise Channel Frequency)
 271  0
                     chan3.setFrequency(value & 0x07);
 272  0
                     currentGenerator = null;
 273  0
                     break;
 274  
                 case 0x07: // 111 rr = 11 (Noise Channel Volume)
 275  0
                     chan3.setVolume(value & 0x0F);
 276  0
                     currentGenerator = null;
 277  0
                     break;
 278  
                 default:
 279  0
                     break;
 280  
             }
 281  0
         } else if (currentGenerator != null) { // Set significant bits of frequency
 282  0
             currentGenerator.setFreqSigf(value);
 283  
         } else {
 284  0
             chan3.setFrequency(value & 0x07);
 285  
         }
 286  0
     }
 287  
     
 288  
     /**
 289  
      * Start emulation.
 290  
      */
 291  
     public synchronized void startSound() {
 292  0
         reset();
 293  0
         enabled = true;
 294  0
         line.start();
 295  0
     }
 296  
     
 297  
     /**
 298  
      * Stop emulation.
 299  
      */
 300  
     public synchronized void stopSound() {
 301  0
         enabled = false;
 302  0
         line.stop();
 303  0
     }
 304  
     
 305  
     
 306  
     /**
 307  
      * Convert PSG settings to Java Sound.
 308  
      */
 309  
     public void output() {
 310  0
         if (!enabled && !recording) {
 311  0
             return;
 312  
         }
 313  
         
 314  
         // Loop for length of this video frame
 315  0
         for (int i = 0; i < samplesPerFrame; i++) {
 316  
             // Sum the channel outputs
 317  0
             int join = 0;
 318  0
             join += chan0.getSample();
 319  0
             join += chan1.getSample();
 320  0
             join += chan2.getSample();
 321  0
             join += chan3.getSample();
 322  
             
 323  
             // Scale volume up
 324  0
             join <<= 1;
 325  
             
 326  
             // Check boundaries
 327  0
             if (join > 0x7F) {
 328  0
                 join = 0x7F;
 329  0
             } else if (join < -0x80) {
 330  0
                 join = -0x80;
 331  
             }
 332  
             
 333  0
             buffer[i] = (byte) join;
 334  
             
 335  0
             if (recording) {
 336  
                 try {
 337  0
                     fileWriter.write(join & 0xff); // output 8 bit signed mono
 338  0
                 } catch (IOException ioe) {
 339  0
                     Logger.getLogger(this.getClass()).error("An error occurred while writing the"
 340  
                             + " sound file.", ioe);
 341  0
                 }
 342  
             }
 343  
         }
 344  
         
 345  
         // Write to Java Line
 346  0
         if (enabled) {
 347  
             try {
 348  
                 // Output Stream write(byte[] b, int off, int len)
 349  
                 // Small buffer to avoid latency, but more intensive CPU usage
 350  0
                 line.write(buffer, 0, samplesPerFrame);
 351  0
             } catch (IllegalArgumentException iae) {
 352  0
                 Logger.getLogger(this.getClass()).error("Error writing to the audio line. "
 353  
                         + "The bytes do not represent complete frames.", iae);
 354  0
             } catch (ArrayIndexOutOfBoundsException aiobe) {
 355  0
                 Logger.getLogger(this.getClass()).error("Error writing to the audio line. "
 356  
                         + "The buffer does not contain the number of bytes specified.", aiobe);
 357  0
             }
 358  
         }
 359  0
     }
 360  
     
 361  
     
 362  
     /**
 363  
      * Toggle sound recording to WAV file.
 364  
      */
 365  
     public void setRecord() {
 366  0
         if (recording) {
 367  0
             stopRecording();
 368  
         } else {
 369  0
             startRecording();
 370  
         }
 371  0
     }
 372  
     
 373  
     
 374  
     /**
 375  
      * Start sound recording to WAV file.
 376  
      */
 377  
     private void startRecording() {
 378  0
         if (!recording) {
 379  
             try {
 380  0
                 fileWriter = new FileWriter("output.raw");
 381  0
             } catch (IOException ioe) {
 382  0
                 Logger.getLogger(this.getClass()).error("Could not open file for recording.", ioe);
 383  0
                 System.out.println("Could not open file for recording");
 384  0
             }
 385  0
             recording = true;
 386  
         }
 387  0
     }
 388  
     
 389  
     
 390  
     /**
 391  
      * Stop sound recording to WAV file.
 392  
      */
 393  
     public void stopRecording() {
 394  0
         if (recording) {
 395  
             try {
 396  0
                 fileWriter.close();
 397  0
                 convertToWav();
 398  0
             } catch (IOException ioe) {
 399  0
                 Logger.getLogger(this.getClass()).error("Failed whilst closing output.raw", ioe);
 400  0
                 System.out.println("Failed whilst closing output.raw");
 401  0
             }
 402  0
             recording = false;
 403  
         }
 404  0
     }
 405  
     
 406  
     /**
 407  
      * Convert RAW output to WAV file.
 408  
      */
 409  
     private void convertToWav() {
 410  0
         File input  = new File("output.raw");
 411  0
         File output = new File("output.wav");
 412  
         
 413  
         try {
 414  0
             FileInputStream fileInputStream = new FileInputStream(input);
 415  
             
 416  0
             AudioInputStream audioInputStream = new AudioInputStream(fileInputStream, audioFormat
 417  
                     , input.length());
 418  0
             AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, output);
 419  0
             audioInputStream.close();
 420  0
             input.delete(); // Delete old file
 421  0
             System.out.println("OUTPUT.WAV recorded");
 422  0
         } catch (IOException ioe) {
 423  0
             Logger.getLogger(this.getClass()).error("Error writing WAV file.", ioe);
 424  0
             System.out.println("Error writing WAV file");
 425  0
         }
 426  0
     }
 427  
     
 428  
 }