Fixes to device initalization caused by recent refactoring, also rewrote speaker playback to be more consistent (sounded horribly on a Macbook Pro)

This commit is contained in:
Brendan Robert 2014-09-18 11:27:35 -05:00
parent 0eb450e9d8
commit dfef919769
5 changed files with 72 additions and 128 deletions

View File

@ -117,8 +117,8 @@ public class Apple2e extends Computer {
} }
motherboard = new Motherboard(this); motherboard = new Motherboard(this);
motherboard.reconfigure(); motherboard.reconfigure();
Motherboard.miscDevices.add(joystick1); motherboard.miscDevices.add(joystick1);
Motherboard.miscDevices.add(joystick2); motherboard.miscDevices.add(joystick2);
} }
@Override @Override

View File

@ -35,8 +35,9 @@ import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.SourceDataLine;
import javax.swing.JFileChooser; import javax.swing.JFileChooser;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import static jace.core.Utility.*;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.util.Timer;
import java.util.TimerTask;
/** /**
* Apple // Speaker Emulation Created on May 9, 2007, 9:55 PM * Apple // Speaker Emulation Created on May 9, 2007, 9:55 PM
@ -128,13 +129,12 @@ public class Speaker extends Device {
* Double-buffer used for playing processed sound -- as one is played the * Double-buffer used for playing processed sound -- as one is played the
* other fills up. * other fills up.
*/ */
byte[] soundBuffer1; byte[] primaryBuffer;
byte[] soundBuffer2; byte[] secondaryBuffer;
int currentBuffer = 1;
int bufferPos = 0; int bufferPos = 0;
Timer playbackTimer;
private double TICKS_PER_SAMPLE = ((double) Motherboard.SPEED) / ((double) SoundMixer.RATE); private double TICKS_PER_SAMPLE = ((double) Motherboard.SPEED) / ((double) SoundMixer.RATE);
private double TICKS_PER_SAMPLE_FLOOR = Math.floor(TICKS_PER_SAMPLE); private double TICKS_PER_SAMPLE_FLOOR = Math.floor(TICKS_PER_SAMPLE);
Thread playbackThread;
private final RAMListener listener private final RAMListener listener
= new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { = new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@ -165,21 +165,22 @@ public class Speaker extends Device {
*/ */
public Speaker(Computer computer) { public Speaker(Computer computer) {
super(computer); super(computer);
configureListener();
reconfigure();
} }
/** /**
* Suspend playback of sound * Suspend playback of sound
*
* @return * @return
*/ */
@Override @Override
public boolean suspend() { public boolean suspend() {
boolean result = super.suspend(); boolean result = super.suspend();
playbackTimer.cancel();
speakerBit = false; speakerBit = false;
if (playbackThread != null && playbackThread.isAlive()) { sdl = null;
playbackThread = null; computer.getMotherboard().cancelSpeedRequest(this);
} computer.getMotherboard().mixer.returnLine(this);
return result; return result;
} }
@ -188,62 +189,42 @@ public class Speaker extends Device {
*/ */
@Override @Override
public void resume() { public void resume() {
System.out.println("Resuming speaker sound");
sdl = null; sdl = null;
try { try {
sdl = computer.getMotherboard().mixer.getLine(this); sdl = computer.getMotherboard().mixer.getLine(this);
sdl.start();
setRun(true);
playbackTimer = new Timer();
playbackTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
playCurrentBuffer();
}
}, 25, 50);
} catch (LineUnavailableException ex) { } catch (LineUnavailableException ex) {
System.out.println("ERROR: Could not output sound: " + ex.getMessage()); System.out.println("ERROR: Could not output sound: " + ex.getMessage());
} }
if (sdl != null) { if (sdl != null) {
setRun(true);
counter = 0; counter = 0;
idleCycles = 0; idleCycles = 0;
level = 0; level = 0;
bufferPos = 0; bufferPos = 0;
if (playbackThread == null || !playbackThread.isAlive()) { }
playbackThread = new Thread(new Runnable() { }
@Override public void playCurrentBuffer() {
public void run() {
int len;
while (isRunning()) {
// Motherboard.requestSpeed(this);
len = bufferPos;
if (len >= MIN_SAMPLE_PLAYBACK) {
byte[] buffer; byte[] buffer;
int len;
synchronized (bufferLock) { synchronized (bufferLock) {
len = bufferPos; len = bufferPos;
buffer = (currentBuffer == 1) ? soundBuffer1 : soundBuffer2; buffer = primaryBuffer;
currentBuffer = (currentBuffer == 1) ? 2 : 1; primaryBuffer = secondaryBuffer;
bufferPos = 0; bufferPos = 0;
} }
secondaryBuffer = buffer;
sdl.write(buffer, 0, len); sdl.write(buffer, 0, len);
if (fileOutputActive && out != null) { sdl.start();
try {
out.write(buffer, 0, len);
} catch (IOException ex) {
Logger.getLogger(Speaker.class.getName()).log(Level.SEVERE, null, ex);
}
}
} else {
try {
// Wait 12.5 ms, which is 1/8 the total duration of the buffer
Thread.sleep(10);
} catch (InterruptedException ex) {
Logger.getLogger(Speaker.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
computer.getMotherboard().cancelSpeedRequest(this);
computer.getMotherboard().mixer.returnLine(this);
}
});
playbackThread.setName("Speaker playback");
playbackThread.start();
}
}
} }
/** /**
@ -263,7 +244,7 @@ public class Speaker extends Device {
*/ */
@Override @Override
public void tick() { public void tick() {
if (!isRunning() || playbackThread == null) { if (!isRunning() || sdl == null) {
return; return;
} }
if (idleCycles++ >= MAX_IDLE_CYCLES) { if (idleCycles++ >= MAX_IDLE_CYCLES) {
@ -278,36 +259,14 @@ public class Speaker extends Device {
int bytes = SoundMixer.BITS >> 3; int bytes = SoundMixer.BITS >> 3;
int shift = SoundMixer.BITS; int shift = SoundMixer.BITS;
// Force emulator to wait until sound buffer has been processed while (bufferPos >= primaryBuffer.length) {
int wait = 0; Thread.yield();
while (bufferPos >= BUFFER_SIZE) {
if (wait++ > 1000) {
computer.pause();
detach();
computer.resume();
Motherboard.enableSpeaker = false;
gripe("Sound playback is not working properly. Check your configuration and sound system to ensure they are set up properly.");
return;
} }
try {
// Yield to other threads (e.g. sound) so that the buffer can drain
Thread.sleep(5);
} catch (InterruptedException ex) {
}
}
byte[] buf;
synchronized (bufferLock) { synchronized (bufferLock) {
if (currentBuffer == 1) {
buf = soundBuffer1;
} else {
buf = soundBuffer2;
}
int index = bufferPos; int index = bufferPos;
for (int i = 0; i < SoundMixer.BITS; i += 8, index++) { for (int i = 0; i < SoundMixer.BITS; i += 8, index++) {
shift -= 8; shift -= 8;
buf[index] = buf[index + bytes] = (byte) ((sample >> shift) & 0x0ff); primaryBuffer[index] = primaryBuffer[index + bytes] = (byte) ((sample >> shift) & 0x0ff);
} }
bufferPos += bytes * 2; bufferPos += bytes * 2;
@ -348,17 +307,18 @@ public class Speaker extends Device {
@Override @Override
public final void reconfigure() { public final void reconfigure() {
if (soundBuffer1 != null && soundBuffer2 != null) { if (primaryBuffer != null && secondaryBuffer != null) {
return; return;
} }
BUFFER_SIZE = 10000 * (SoundMixer.BITS >> 3); BUFFER_SIZE = 10000 * (SoundMixer.BITS >> 3);
MIN_SAMPLE_PLAYBACK = SoundMixer.BITS * 8; MIN_SAMPLE_PLAYBACK = SoundMixer.BITS * 8;
soundBuffer1 = new byte[BUFFER_SIZE]; primaryBuffer = new byte[BUFFER_SIZE];
soundBuffer2 = new byte[BUFFER_SIZE]; secondaryBuffer = new byte[BUFFER_SIZE];
} }
@Override @Override
public void attach() { public void attach() {
reconfigure();
configureListener(); configureListener();
resume(); resume();
} }

View File

@ -49,7 +49,7 @@ public abstract class Computer implements Reconfigurable {
* Creates a new instance of Computer * Creates a new instance of Computer
*/ */
public Computer() { public Computer() {
keyboard = new Keyboard(); keyboard = new Keyboard(this);
} }
public RAM getMemory() { public RAM getMemory() {

View File

@ -23,6 +23,7 @@ import jace.apple2e.Speaker;
import jace.config.ConfigurableField; import jace.config.ConfigurableField;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -38,7 +39,7 @@ import java.util.Set;
*/ */
public class Motherboard extends TimedDevice { public class Motherboard extends TimedDevice {
static final public Set<Device> miscDevices = new HashSet<>(); final public Set<Device> miscDevices = new LinkedHashSet<>();
@ConfigurableField(name = "Enable Speaker", shortName = "speaker", defaultValue = "true") @ConfigurableField(name = "Enable Speaker", shortName = "speaker", defaultValue = "true")
public static boolean enableSpeaker = true; public static boolean enableSpeaker = true;
public Speaker speaker; public Speaker speaker;
@ -74,10 +75,10 @@ public class Motherboard extends TimedDevice {
@ConfigurableField(category = "advanced", name = "CPU per clock", defaultValue = "1", description = "Number of CPU cycles per clock cycle (normal = 1)") @ConfigurableField(category = "advanced", name = "CPU per clock", defaultValue = "1", description = "Number of CPU cycles per clock cycle (normal = 1)")
public static int cpuPerClock = 1; public static int cpuPerClock = 1;
public int clockCounter = 1; public int clockCounter = 1;
public Card[] cards;
@Override @Override
public void tick() { public void tick() {
Card[] cards = computer.getMemory().getAllCards();
try { try {
clockCounter--; clockCounter--;
computer.getCpu().doTick(); computer.getCpu().doTick();
@ -130,30 +131,32 @@ public class Motherboard extends TimedDevice {
boolean startAgain = pause(); boolean startAgain = pause();
accelorationRequestors.clear(); accelorationRequestors.clear();
super.reconfigure(); super.reconfigure();
Card[] cards = computer.getMemory().getAllCards();
// Now create devices as needed, e.g. sound // Now create devices as needed, e.g. sound
Motherboard.miscDevices.add(mixer); miscDevices.add(mixer);
mixer.reconfigure(); mixer.reconfigure();
if (enableSpeaker) { if (enableSpeaker) {
try { try {
if (speaker == null) { if (speaker == null) {
speaker = new Speaker(computer); speaker = new Speaker(computer);
} else {
speaker.attach();
} }
if (mixer.lineAvailable) { if (mixer.lineAvailable) {
Motherboard.miscDevices.add(speaker); speaker.reconfigure();
speaker.attach();
miscDevices.add(speaker);
} else {
System.out.print("No lines available! Speaker not running.");
} }
} catch (Throwable t) { } catch (Throwable t) {
System.out.println("Unable to initalize sound -- deactivating speaker out"); System.out.println("Unable to initalize sound -- deactivating speaker out");
speaker.detach(); speaker.detach();
Motherboard.miscDevices.remove(speaker); miscDevices.remove(speaker);
} }
} else { } else {
System.out.println("Speaker not enabled, leaving it off.");
if (speaker != null) { if (speaker != null) {
speaker.detach(); speaker.detach();
Motherboard.miscDevices.remove(speaker); miscDevices.remove(speaker);
} }
} }
if (startAgain && computer.getMemory() != null) { if (startAgain && computer.getMemory() != null) {
@ -177,17 +180,19 @@ public class Motherboard extends TimedDevice {
@Override @Override
public void attach() { public void attach() {
} }
Map<Card, Boolean> resume = new HashMap<>(); final Set<Card> resume = new HashSet<>();
@Override @Override
public boolean suspend() { public boolean suspend() {
synchronized (resume) { synchronized (resume) {
resume.clear(); resume.clear();
for (Card c : cards) { for (Card c : computer.getMemory().getAllCards()) {
if (c == null || !c.suspendWithCPU() || !c.isRunning()) { if (c == null || !c.suspendWithCPU() || !c.isRunning()) {
continue; continue;
} }
resume.put(c, c.suspend()); if (c.suspend()) {
resume.add(c);
}
} }
} }
return super.suspend(); return super.suspend();
@ -195,22 +200,21 @@ public class Motherboard extends TimedDevice {
@Override @Override
public void resume() { public void resume() {
cards = computer.getMemory().getAllCards();
super.resume(); super.resume();
synchronized (resume) { synchronized (resume) {
for (Card c : cards) { resume.stream().forEach((c) -> {
if (Boolean.TRUE.equals(resume.get(c))) {
c.resume(); c.resume();
} });
}
} }
} }
@Override @Override
public void detach() { public void detach() {
for (Device d : miscDevices) { System.out.println("Detaching motherboard");
miscDevices.stream().forEach((d) -> {
d.suspend(); d.suspend();
} d.detach();
});
miscDevices.clear(); miscDevices.clear();
// halt(); // halt();
} }

View File

@ -108,7 +108,6 @@ public class SoundMixer extends Device {
initMixer(); initMixer();
if (lineAvailable) { if (lineAvailable) {
initAudio(); initAudio();
System.out.println("Started sound");
} else { } else {
System.out.println("Sound not stared: Line not available"); System.out.println("Sound not stared: Line not available");
} }
@ -153,8 +152,8 @@ public class SoundMixer extends Device {
SourceDataLine sdl = activeLines.remove(requester); SourceDataLine sdl = activeLines.remove(requester);
// Calling drain on pulse driver can cause it to freeze up (?) // Calling drain on pulse driver can cause it to freeze up (?)
// sdl.drain(); // sdl.drain();
sdl.stop();
sdl.flush(); sdl.flush();
sdl.stop();
availableLines.add(sdl); availableLines.add(sdl);
} }
} }
@ -180,26 +179,7 @@ public class SoundMixer extends Device {
throw new LineUnavailableException("Line is not an output line!"); throw new LineUnavailableException("Line is not an output line!");
} }
final SourceDataLine sdl = (SourceDataLine) l; final SourceDataLine sdl = (SourceDataLine) l;
// sdl.open(af);
// if (false) {
// return sdl;
// }
sdl.open(); sdl.open();
sdl.start();
// new Thread(new Runnable() {
// @Override
// public void run() {
// System.out.println("Going into an infinite loop!!!");
// try {
// while (true) {
// sdl.write(new byte[]{randomByte(),randomByte(),randomByte(),randomByte()}, 0, 4);
// }
// } catch (Throwable t) {
// t.printStackTrace();
// }
// System.out.println("Thread dying...");
// }
// }).start();
return sdl; return sdl;
} }