diff --git a/src/main/java/jace/apple2e/Apple2e.java b/src/main/java/jace/apple2e/Apple2e.java index b6b1afc..968d5ae 100644 --- a/src/main/java/jace/apple2e/Apple2e.java +++ b/src/main/java/jace/apple2e/Apple2e.java @@ -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 diff --git a/src/main/java/jace/apple2e/Speaker.java b/src/main/java/jace/apple2e/Speaker.java index f170709..cd439d4 100644 --- a/src/main/java/jace/apple2e/Speaker.java +++ b/src/main/java/jace/apple2e/Speaker.java @@ -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(); } diff --git a/src/main/java/jace/core/Computer.java b/src/main/java/jace/core/Computer.java index 071ec5e..a7a855b 100644 --- a/src/main/java/jace/core/Computer.java +++ b/src/main/java/jace/core/Computer.java @@ -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() { diff --git a/src/main/java/jace/core/Motherboard.java b/src/main/java/jace/core/Motherboard.java index d1beab8..3c1a8ff 100644 --- a/src/main/java/jace/core/Motherboard.java +++ b/src/main/java/jace/core/Motherboard.java @@ -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 miscDevices = new HashSet<>(); + final public Set 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 resume = new HashMap<>(); + final Set 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(); } diff --git a/src/main/java/jace/core/SoundMixer.java b/src/main/java/jace/core/SoundMixer.java index 7f1bd32..0d1f843 100644 --- a/src/main/java/jace/core/SoundMixer.java +++ b/src/main/java/jace/core/SoundMixer.java @@ -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; }