/*
JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine
Release Version 2.4
A project from the Physics Dept, The University of Oxford
Copyright (C) 2007-2010 The University of Oxford
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Details (including contact information) can be found at:
jpc.sourceforge.net
or the developer website
sourceforge.net/projects/jpc/
Conceived and Developed by:
Rhys Newman, Ian Preston, Chris Dennis
End of licence header
*/
package org.jpc.emulator.peripheral;
import org.jpc.emulator.motherboard.*;
import org.jpc.emulator.*;
import javax.sound.midi.*;
import java.io.*;
import java.net.URI;
import java.util.logging.*;
/**
*
* @author Ian Preston
*/
public class PCSpeaker extends AbstractHardwareComponent implements IODevice
{
private static final Logger LOGGING = Logger.getLogger(PCSpeaker.class.getName());
private static final int SPEAKER_SAMPLE_RATE = 22050;
private static final int SPEAKER_MAX_FREQ = SPEAKER_SAMPLE_RATE >> 1;
private static final int SPEAKER_MIN_FREQ = 10;
private static final int SPEAKER_VOLUME = 16000;
private static final int SPEAKER_OFF = 0, SPEAKER_ON = 2, SPEAKER_PIT_ON = 3, SPEAKER_PIT_OFF = 1;
private int dummyRefreshClock, speakerOn, lastNote, currentNote, velocity = 90, waitingForPit;
private IntervalTimer pit;
private boolean enabled = false, ioportRegistered;
private Synthesizer synthesizer;
private Receiver receiver;
private ShortMessage message = new ShortMessage();
private Instrument[] instruments;
private MidiChannel cc; // current channel
public int mode;
public PCSpeaker()
{
ioportRegistered = false;
if (enabled)
{
configure();
}
}
public void enable(boolean value)
{
if (!value)
{
enabled = false;
}
else
{
enabled = true;
configure();
}
}
private void configure()
{
try {
if (synthesizer == null) {
if ((synthesizer = MidiSystem.getSynthesizer()) == null) {
LOGGING.log(Level.INFO, "couldn't get MIDI synthesizer failed");
enabled = false;
return;
}
}
synthesizer.open();
receiver = synthesizer.getReceiver();
} catch (MidiUnavailableException e) {
LOGGING.log(Level.INFO, "pc speaker disabled", e);
enabled = false;
return;
} catch (Exception e) {
LOGGING.log(Level.INFO, "pc speaker disabled", e);
enabled = false;
return;
}
Soundbank sb = synthesizer.getDefaultSoundbank();
if (sb == null)
{
System.out.println("Warning: loading remote soundbank.");
try
{
sb = MidiSystem.getSoundbank(new URI("http://www.classicdosgames.com/soundbank.gm").toURL());
} catch (Exception e) {e.printStackTrace();}
}
if (sb != null)
{
instruments = sb.getInstruments();
synthesizer.loadInstrument(instruments[0]);
}
MidiChannel[] channels = synthesizer.getChannels();
cc = channels[0];
programChange(80); //80 = load square wave instrument
}
private int getNote()
{
double freq = IntervalTimer.PIT_FREQ/pit.getInitialCount(2); //actual frequency in Hz
if (freq > SPEAKER_MAX_FREQ)
freq = SPEAKER_MAX_FREQ;
if (freq < SPEAKER_MIN_FREQ)
freq = SPEAKER_MIN_FREQ;
return frequencyToNote(freq);
}
public static int frequencyToNote(double f)
{
double ans = 12*(Math.log(f) - Math.log(440))/Math.log(2);
return (int) ans + 69;
}
private void playNote(int note)
{
try {
message.setMessage(ShortMessage.NOTE_ON, 0, note, velocity);
} catch (InvalidMidiDataException e) {e.printStackTrace();}
receiver.send(message, -1);
}
private void stopNote(int note)
{
try {
message.setMessage(ShortMessage.NOTE_OFF, 0, note, velocity);
} catch (InvalidMidiDataException e) {e.printStackTrace();}
receiver.send(message, -1);
}
public synchronized void play()
{
waitingForPit++;
if ((enabled) && (waitingForPit == 2))
{
if (pit.getMode(2) != 3)
return;
lastNote = currentNote;
currentNote = getNote();
stopNote(lastNote);
playNote(currentNote);
}
}
private void programChange(int program) {
if (instruments != null) {
synthesizer.loadInstrument(instruments[program]);
}
cc.programChange(program);
}
public void saveState(DataOutput output) throws IOException
{
output.writeInt(dummyRefreshClock);
output.writeInt(speakerOn);
}
public void loadState(DataInput input) throws IOException
{
ioportRegistered = false;
dummyRefreshClock = input.readInt();
speakerOn = input.readInt();
}
public int[] ioPortsRequested()
{
return new int[]{0x61};
}
public int ioPortRead8(int address)
{
int out = pit.getOut(2);
dummyRefreshClock ^= 1;
return (speakerOn << 1) | (pit.getGate(2) ? 1 : 0) | (out << 5) |
(dummyRefreshClock << 4);
}
public int ioPortRead16(int address)
{
return (0xff & ioPortRead8(address)) |
(0xff00 & (ioPortRead8(address + 1) << 8));
}
public int ioPortRead32(int address)
{
return (0xffff & ioPortRead16(address)) |
(0xffff0000 & (ioPortRead16(address + 2) << 16));
}
public synchronized void ioPortWrite8(int address, int data)
{
if (!enabled)
return;
speakerOn = (data >> 1) & 1;
pit.setGate(2, (data & 1) != 0);
if ((data & 1 ) == 1)
{
if (speakerOn == 1)
{
//connect speaker to PIT
mode = SPEAKER_PIT_ON;
waitingForPit = 0;
//play();
}
else
{
//leave speaker disconnected from following PIT
mode = SPEAKER_PIT_OFF;
stopNote(currentNote);
}
}
else
{
// zero bit is 0, speaker follows bit 1
mode = SPEAKER_OFF;
stopNote(currentNote);
if (speakerOn != 0)
LOGGING.log(Level.INFO, "manual speaker management not implemented");
}
}
public void ioPortWrite16(int address, int data)
{
this.ioPortWrite8(address, data);
this.ioPortWrite8(address + 1, data >> 8);
}
public void ioPortWrite32(int address, int data)
{
this.ioPortWrite16(address, data);
this.ioPortWrite16(address + 2, data >> 16);
}
public boolean initialised()
{
return ioportRegistered && (pit != null);
}
public void reset()
{
pit = null;
ioportRegistered = false;
}
public boolean updated()
{
return ioportRegistered && pit.updated();
}
public void updateComponent(HardwareComponent component)
{
if ((component instanceof IOPortHandler) && component.updated())
{
((IOPortHandler)component).registerIOPortCapable(this);
ioportRegistered = true;
}
}
public void acceptComponent(HardwareComponent component)
{
if ((component instanceof IntervalTimer) &&
component.initialised()) {
pit = (IntervalTimer)component;
}
if ((component instanceof IOPortHandler)
&& component.initialised()) {
((IOPortHandler)component).registerIOPortCapable(this);
ioportRegistered = true;
}
}
}