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 = 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
|
||||||
|
@ -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,64 +189,44 @@ 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 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
|
* Reset idle counter whenever sound playback occurs
|
||||||
*/
|
*/
|
||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user