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.reconfigure();
Motherboard.miscDevices.add(joystick1);
Motherboard.miscDevices.add(joystick2);
motherboard.miscDevices.add(joystick1);
motherboard.miscDevices.add(joystick2);
}
@Override

View File

@ -35,8 +35,9 @@ import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import static jace.core.Utility.*;
import java.io.FileNotFoundException;
import java.util.Timer;
import java.util.TimerTask;
/**
* 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
* other fills up.
*/
byte[] soundBuffer1;
byte[] soundBuffer2;
int currentBuffer = 1;
byte[] primaryBuffer;
byte[] secondaryBuffer;
int bufferPos = 0;
Timer playbackTimer;
private double TICKS_PER_SAMPLE = ((double) Motherboard.SPEED) / ((double) SoundMixer.RATE);
private double TICKS_PER_SAMPLE_FLOOR = Math.floor(TICKS_PER_SAMPLE);
Thread playbackThread;
private final RAMListener listener
= new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@ -165,21 +165,22 @@ public class Speaker extends Device {
*/
public Speaker(Computer computer) {
super(computer);
configureListener();
reconfigure();
}
/**
* Suspend playback of sound
* @return
*
* @return
*/
@Override
public boolean suspend() {
boolean result = super.suspend();
playbackTimer.cancel();
speakerBit = false;
if (playbackThread != null && playbackThread.isAlive()) {
playbackThread = null;
}
sdl = null;
computer.getMotherboard().cancelSpeedRequest(this);
computer.getMotherboard().mixer.returnLine(this);
return result;
}
@ -188,64 +189,44 @@ public class Speaker extends Device {
*/
@Override
public void resume() {
System.out.println("Resuming speaker sound");
sdl = null;
try {
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) {
System.out.println("ERROR: Could not output sound: " + ex.getMessage());
}
if (sdl != null) {
setRun(true);
counter = 0;
idleCycles = 0;
level = 0;
bufferPos = 0;
if (playbackThread == null || !playbackThread.isAlive()) {
playbackThread = new Thread(new Runnable() {
@Override
public void run() {
int len;
while (isRunning()) {
// Motherboard.requestSpeed(this);
len = bufferPos;
if (len >= MIN_SAMPLE_PLAYBACK) {
byte[] buffer;
synchronized (bufferLock) {
len = bufferPos;
buffer = (currentBuffer == 1) ? soundBuffer1 : soundBuffer2;
currentBuffer = (currentBuffer == 1) ? 2 : 1;
bufferPos = 0;
}
sdl.write(buffer, 0, len);
if (fileOutputActive && out != null) {
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();
}
}
}
public void playCurrentBuffer() {
byte[] buffer;
int len;
synchronized (bufferLock) {
len = bufferPos;
buffer = primaryBuffer;
primaryBuffer = secondaryBuffer;
bufferPos = 0;
}
secondaryBuffer = buffer;
sdl.write(buffer, 0, len);
sdl.start();
}
/**
* Reset idle counter whenever sound playback occurs
*/
@ -263,7 +244,7 @@ public class Speaker extends Device {
*/
@Override
public void tick() {
if (!isRunning() || playbackThread == null) {
if (!isRunning() || sdl == null) {
return;
}
if (idleCycles++ >= MAX_IDLE_CYCLES) {
@ -278,36 +259,14 @@ public class Speaker extends Device {
int bytes = SoundMixer.BITS >> 3;
int shift = SoundMixer.BITS;
// Force emulator to wait until sound buffer has been processed
int wait = 0;
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) {
}
while (bufferPos >= primaryBuffer.length) {
Thread.yield();
}
byte[] buf;
synchronized (bufferLock) {
if (currentBuffer == 1) {
buf = soundBuffer1;
} else {
buf = soundBuffer2;
}
int index = bufferPos;
for (int i = 0; i < SoundMixer.BITS; i += 8, index++) {
shift -= 8;
buf[index] = buf[index + bytes] = (byte) ((sample >> shift) & 0x0ff);
primaryBuffer[index] = primaryBuffer[index + bytes] = (byte) ((sample >> shift) & 0x0ff);
}
bufferPos += bytes * 2;
@ -348,17 +307,18 @@ public class Speaker extends Device {
@Override
public final void reconfigure() {
if (soundBuffer1 != null && soundBuffer2 != null) {
if (primaryBuffer != null && secondaryBuffer != null) {
return;
}
BUFFER_SIZE = 10000 * (SoundMixer.BITS >> 3);
MIN_SAMPLE_PLAYBACK = SoundMixer.BITS * 8;
soundBuffer1 = new byte[BUFFER_SIZE];
soundBuffer2 = new byte[BUFFER_SIZE];
primaryBuffer = new byte[BUFFER_SIZE];
secondaryBuffer = new byte[BUFFER_SIZE];
}
@Override
public void attach() {
reconfigure();
configureListener();
resume();
}

View File

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

View File

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

View File

@ -108,7 +108,6 @@ public class SoundMixer extends Device {
initMixer();
if (lineAvailable) {
initAudio();
System.out.println("Started sound");
} else {
System.out.println("Sound not stared: Line not available");
}
@ -153,8 +152,8 @@ public class SoundMixer extends Device {
SourceDataLine sdl = activeLines.remove(requester);
// Calling drain on pulse driver can cause it to freeze up (?)
// sdl.drain();
sdl.stop();
sdl.flush();
sdl.stop();
availableLines.add(sdl);
}
}
@ -180,26 +179,7 @@ public class SoundMixer extends Device {
throw new LineUnavailableException("Line is not an output line!");
}
final SourceDataLine sdl = (SourceDataLine) l;
// sdl.open(af);
// if (false) {
// return sdl;
// }
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;
}