diff --git a/src/main/java/jace/apple2e/Speaker.java b/src/main/java/jace/apple2e/Speaker.java index b9a3abc..de55001 100644 --- a/src/main/java/jace/apple2e/Speaker.java +++ b/src/main/java/jace/apple2e/Speaker.java @@ -177,7 +177,7 @@ public class Speaker extends Device { speakerBit = false; sdl = null; computer.getMotherboard().cancelSpeedRequest(this); - computer.getMotherboard().mixer.returnLine(this); + computer.mixer.returnLine(this); return result; } @@ -189,15 +189,16 @@ public class Speaker extends Device { public void resume() { if (sdl != null && isRunning()) return; System.out.println("Resuming speaker sound"); - sdl = null; try { - sdl = computer.getMotherboard().mixer.getLine(this); + if (sdl == null || !sdl.isOpen()) { + sdl = computer.mixer.getLine(this); + } sdl.start(); + setRun(true); counter = 0; idleCycles = 0; level = 0; bufferPos = 0; - setRun(true); playbackTimer = new Timer(); playbackTimer.scheduleAtFixedRate(new TimerTask() { @Override diff --git a/src/main/java/jace/core/Computer.java b/src/main/java/jace/core/Computer.java index 6a0d4ae..63ea451 100644 --- a/src/main/java/jace/core/Computer.java +++ b/src/main/java/jace/core/Computer.java @@ -41,14 +41,18 @@ public abstract class Computer implements Reconfigurable { public Keyboard keyboard; public StateManager stateManager; public Motherboard motherboard; + public boolean romLoaded; @ConfigurableField(category = "advanced", name = "State management", shortName = "rewind", description = "This enables rewind support, but consumes a lot of memory when active.") public boolean enableStateManager; + public final SoundMixer mixer; /** * Creates a new instance of Computer */ public Computer() { keyboard = new Keyboard(this); + mixer = new SoundMixer(this); + romLoaded = false; } public RAM getMemory() { @@ -100,12 +104,14 @@ public abstract class Computer implements Reconfigurable { public void loadRom(String path) throws IOException { memory.loadRom(path); + romLoaded = true; } public void deactivate() { cpu.suspend(); motherboard.suspend(); video.suspend(); + mixer.detach(); } @InvokableAction( @@ -116,7 +122,18 @@ public abstract class Computer implements Reconfigurable { consumeKeyEvent = true, defaultKeyMapping = {"Ctrl+Shift+Backspace","Ctrl+Shift+Delete"}) public void invokeColdStart() { - coldStart(); + if (!romLoaded) { + System.out.println("Computer booted before rom was loaded"); + Thread delayedStart = new Thread(() -> { + while (!romLoaded) { + Thread.yield(); + } + coldStart(); + }); + delayedStart.start(); + } else { + coldStart(); + } } public abstract void coldStart(); @@ -155,6 +172,7 @@ public abstract class Computer implements Reconfigurable { @Override public void reconfigure() { + mixer.reconfigure(); if (enableStateManager) { stateManager = StateManager.getInstance(this); } else { diff --git a/src/main/java/jace/core/Motherboard.java b/src/main/java/jace/core/Motherboard.java index 40ce6e3..d12b587 100644 --- a/src/main/java/jace/core/Motherboard.java +++ b/src/main/java/jace/core/Motherboard.java @@ -42,7 +42,6 @@ public class Motherboard extends TimedDevice { @ConfigurableField(name = "Enable Speaker", shortName = "speaker", defaultValue = "true") public static boolean enableSpeaker = true; public Speaker speaker; - public SoundMixer mixer; void vblankEnd() { SoftSwitches.VBL.getSwitch().setState(true); @@ -59,7 +58,6 @@ public class Motherboard extends TimedDevice { */ public Motherboard(Computer computer) { super(computer); - mixer = new SoundMixer(computer); } @Override @@ -112,21 +110,19 @@ public class Motherboard extends TimedDevice { accelorationRequestors.clear(); super.reconfigure(); // Now create devices as needed, e.g. sound - miscDevices.add(mixer); - mixer.reconfigure(); if (enableSpeaker) { try { if (speaker == null) { speaker = new Speaker(computer); + if (computer.mixer.lineAvailable) { + speaker.attach(); + miscDevices.add(speaker); + } else { + System.out.print("No lines available! Speaker not running."); + } } - if (mixer.lineAvailable) { - speaker.reconfigure(); - speaker.attach(); - miscDevices.add(speaker); - } else { - System.out.print("No lines available! Speaker not running."); - } + speaker.reconfigure(); } catch (Throwable t) { System.out.println("Unable to initalize sound -- deactivating speaker out"); speaker.detach(); @@ -167,7 +163,9 @@ public class Motherboard extends TimedDevice { synchronized (resume) { resume.clear(); for (Optional c : computer.getMemory().getAllCards()) { - if (!c.isPresent()) continue; + if (!c.isPresent()) { + continue; + } if (!c.get().suspendWithCPU() || !c.get().isRunning()) { continue; } diff --git a/src/main/java/jace/core/SoundMixer.java b/src/main/java/jace/core/SoundMixer.java index a186d14..e531299 100644 --- a/src/main/java/jace/core/SoundMixer.java +++ b/src/main/java/jace/core/SoundMixer.java @@ -44,7 +44,7 @@ import javax.sound.sampled.SourceDataLine; * how to reuse active lines if needed. It is possible that this class might be * used to manage volume in the future, but that remains to be seen. * - * @author Brendan Robert (BLuRry) brendan.robert@gmail.com + * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ public class SoundMixer extends Device { @@ -103,19 +103,25 @@ public class SoundMixer extends Device { @Override public synchronized void reconfigure() { - detach(); - try { - initMixer(); - if (lineAvailable) { - initAudio(); - } else { - System.out.println("Sound not stared: Line not available"); + if (isConfigDifferent()) { + detach(); + try { + initMixer(); + if (lineAvailable) { + initAudio(); + } else { + System.out.println("Sound not stared: Line not available"); + } + } catch (LineUnavailableException ex) { + System.out.println("Unable to start sound"); + Logger.getLogger(SoundMixer.class.getName()).log(Level.SEVERE, null, ex); } - } catch (LineUnavailableException ex) { - System.out.println("Unable to start sound"); - Logger.getLogger(SoundMixer.class.getName()).log(Level.SEVERE, null, ex); + attach(); } - attach(); + } + + private AudioFormat getAudioFormat() { + return new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, RATE, BITS, 2, BITS / 4, RATE, true); } /** @@ -125,8 +131,7 @@ public class SoundMixer extends Device { * available */ private void initAudio() throws LineUnavailableException { - af = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, RATE, BITS, 2, BITS / 4, RATE, true); -// af = new AudioFormat(RATE, BITS, 2, true, true); + af = getAudioFormat(); DataLine.Info dli = new DataLine.Info(SourceDataLine.class, af); lineAvailable = AudioSystem.isLineSupported(dli); } @@ -152,8 +157,10 @@ public class SoundMixer extends Device { SourceDataLine sdl = activeLines.remove(requester); // Calling drain on pulse driver can cause it to freeze up (?) // sdl.drain(); - sdl.flush(); - sdl.stop(); + if (sdl.isRunning()) { + sdl.flush(); + sdl.stop(); + } availableLines.add(sdl); } } @@ -213,7 +220,9 @@ public class SoundMixer extends Device { }); if (theMixer != null) { for (Line l : theMixer.getSourceLines()) { - l.close(); +// if (l.isOpen()) { +// l.close(); +// } } } availableLines.clear(); @@ -241,9 +250,24 @@ public class SoundMixer extends Device { } } theMixer = AudioSystem.getMixer(selected); - for (Line l : theMixer.getSourceLines()) { - l.close(); - } +// for (Line l : theMixer.getSourceLines()) { +// l.close(); +// } lineAvailable = true; } + + String oldPreferredMixer = null; + + private boolean isConfigDifferent() { + boolean changed = false; + AudioFormat newAf = getAudioFormat(); + changed |= (af == null || !newAf.matches(af)); + if (oldPreferredMixer == null) { + changed |= preferredMixer.getValue() != null; + } else { + changed |= !oldPreferredMixer.matches(preferredMixer.getValue()); + } + oldPreferredMixer = preferredMixer.getValue(); + return changed; + } } diff --git a/src/main/java/jace/hardware/CardMockingboard.java b/src/main/java/jace/hardware/CardMockingboard.java index 29770e2..d1c72c6 100644 --- a/src/main/java/jace/hardware/CardMockingboard.java +++ b/src/main/java/jace/hardware/CardMockingboard.java @@ -30,12 +30,14 @@ import jace.core.SoundMixer; import static jace.core.Utility.*; import jace.hardware.mockingboard.PSG; import jace.hardware.mockingboard.R6522; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; +import javafx.application.Platform; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; @@ -50,7 +52,6 @@ import javax.sound.sampled.SourceDataLine; public class CardMockingboard extends Card implements Runnable { // If true, emulation will cover 4 AY chips. Otherwise, only 2 AY chips - static final int[] AY_ADDRESSES = new int[]{0, 0x080, 0x010, 0x090}; @ConfigurableField(name = "Volume", shortName = "vol", category = "Sound", description = "Mockingboard volume, 100=max, 0=silent") @@ -93,27 +94,33 @@ public class CardMockingboard extends Card implements Runnable { //don't ask... final int j = i; controllers[i] = new R6522(computer) { + int controller = j; + @Override public void sendOutputA(int value) { - if (activeChip != null) { - activeChip.setBus(value); - } else { - System.out.println("No active AY chip!"); + chips[j].setBus(value); + if (phasorMode) { + chips[j+2].setBus(value); } } @Override public void sendOutputB(int value) { - if (activeChip != null) { - activeChip.setControl(value & 0x07); + if (phasorMode) { + if ((chips[j].mask & value) != 0) { + chips[j].setControl(value & 0x07); + } + if ((chips[j+2].mask & value) != 0) { + chips[j+2].setControl(value & 0x07); + } } else { - System.out.println("No active AY chip!"); + chips[j].setControl(value & 0x07); } } @Override public int receiveOutputA() { - return activeChip == null ? 0 : activeChip.bus; + return chips[j] == null ? 0 : chips[j].bus; } @Override @@ -140,22 +147,19 @@ public class CardMockingboard extends Card implements Runnable { } } RAMListener mainListener = null; - PSG activeChip = null; @Override protected void handleFirmwareAccess(int register, TYPE type, int value, RAMEvent e) { // System.out.println(e.getType().toString() + " event to mockingboard register "+Integer.toHexString(register)+", value "+e.getNewValue()); - activeChip = null; resume(); int chip = 0; for (PSG psg : chips) { if (psg.getBaseReg() == (register & 0x0f0)) { - activeChip = psg; break; } chip++; } - if (activeChip == null) { + if (chip >= 2) { System.err.println("Could not determine which PSG to communicate to"); e.setNewValue(computer.getVideo().getFloatingBus()); return; @@ -213,7 +217,7 @@ public class CardMockingboard extends Card implements Runnable { boolean restart = suspend(); initPSG(); for (PSG chip : chips) { - chip.setRate(CLOCK_SPEED, SAMPLE_RATE); + chip.setRate(phasorMode ? CLOCK_SPEED*2 : CLOCK_SPEED, SAMPLE_RATE); chip.reset(); } super.reconfigure(); @@ -260,7 +264,7 @@ public class CardMockingboard extends Card implements Runnable { } Thread playbackThread = null; boolean pause = false; - + @Override public void resume() { pause = false; @@ -308,7 +312,7 @@ public class CardMockingboard extends Card implements Runnable { */ public void run() { try { - SourceDataLine out = computer.getMotherboard().mixer.getLine(this); + SourceDataLine out = computer.mixer.getLine(this); int[] leftBuffer = new int[BUFFER_LENGTH]; int[] rightBuffer = new int[BUFFER_LENGTH]; int frameSize = out.getFormat().getFrameSize(); @@ -319,6 +323,7 @@ public class CardMockingboard extends Card implements Runnable { ticksBeteenPlayback = (int) ((Motherboard.SPEED * BUFFER_LENGTH) / SAMPLE_RATE); ticksSinceLastPlayback = 0; int zeroSamples = 0; + setRun(true); while (isRunning()) { computer.getMotherboard().requestSpeed(this); playSound(leftBuffer, rightBuffer); @@ -385,15 +390,21 @@ public class CardMockingboard extends Card implements Runnable { } finally { computer.getMotherboard().cancelSpeedRequest(this); System.out.println("Mockingboard playback stopped"); - computer.getMotherboard().mixer.returnLine(this); + computer.mixer.returnLine(this); } } private void initPSG() { - int max = phasorMode ? 4 : 2; - chips = new PSG[max]; - for (int i = 0; i < max; i++) { - chips[i] = new PSG(AY_ADDRESSES[i], CLOCK_SPEED, SAMPLE_RATE, "AY" + i); + if (phasorMode) { + chips = new PSG[4]; + chips[0] = new PSG(0x10, CLOCK_SPEED*2, SAMPLE_RATE, "AY1", 8); + chips[1] = new PSG(0x80, CLOCK_SPEED*2, SAMPLE_RATE, "AY2", 8); + chips[2] = new PSG(0x10, CLOCK_SPEED*2, SAMPLE_RATE, "AY3", 16); + chips[3] = new PSG(0x80, CLOCK_SPEED*2, SAMPLE_RATE, "AY4", 16); + } else { + chips = new PSG[2]; + chips[0] = new PSG(0, CLOCK_SPEED, SAMPLE_RATE, "AY1", 255); + chips[1] = new PSG(0x80, CLOCK_SPEED, SAMPLE_RATE, "AY2", 255); } } diff --git a/src/main/java/jace/hardware/mockingboard/PSG.java b/src/main/java/jace/hardware/mockingboard/PSG.java index 4911266..ed9d600 100644 --- a/src/main/java/jace/hardware/mockingboard/PSG.java +++ b/src/main/java/jace/hardware/mockingboard/PSG.java @@ -106,9 +106,11 @@ public class PSG { int selectedReg; String name; Map regValues; + public int mask; - public PSG(int base, int clock, int sample_rate, String name) { + public PSG(int base, int clock, int sample_rate, String name, int DDR_Mask) { this.name = name; + this.mask = DDR_Mask; baseReg = base; channels = new ArrayList<>(); for (int i = 0; i < 3; i++) {