forked from Apple-2-Tools/jace
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:
parent
0eb450e9d8
commit
dfef919769
@ -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
|
||||
|
@ -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
|
||||
*/
|
||||
@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();
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user