Moved mixer to Computer to avoid getting it blown away every time Motherboard is reset. Also: FULL SUPPORT of the AE Phasor is now working and tested. Partial support for multiple mockingboards is present but they are not synchronized so it sounds really strange but at least does not crash.

This commit is contained in:
Brendan Robert 2015-03-29 00:22:57 -05:00
parent 5416b0a787
commit b0272f7d6a
6 changed files with 113 additions and 59 deletions

View File

@ -177,7 +177,7 @@ public class Speaker extends Device {
speakerBit = false; speakerBit = false;
sdl = null; sdl = null;
computer.getMotherboard().cancelSpeedRequest(this); computer.getMotherboard().cancelSpeedRequest(this);
computer.getMotherboard().mixer.returnLine(this); computer.mixer.returnLine(this);
return result; return result;
} }
@ -189,15 +189,16 @@ public class Speaker extends Device {
public void resume() { public void resume() {
if (sdl != null && isRunning()) return; if (sdl != null && isRunning()) return;
System.out.println("Resuming speaker sound"); System.out.println("Resuming speaker sound");
sdl = null;
try { try {
sdl = computer.getMotherboard().mixer.getLine(this); if (sdl == null || !sdl.isOpen()) {
sdl = computer.mixer.getLine(this);
}
sdl.start(); sdl.start();
setRun(true);
counter = 0; counter = 0;
idleCycles = 0; idleCycles = 0;
level = 0; level = 0;
bufferPos = 0; bufferPos = 0;
setRun(true);
playbackTimer = new Timer(); playbackTimer = new Timer();
playbackTimer.scheduleAtFixedRate(new TimerTask() { playbackTimer.scheduleAtFixedRate(new TimerTask() {
@Override @Override

View File

@ -41,14 +41,18 @@ public abstract class Computer implements Reconfigurable {
public Keyboard keyboard; public Keyboard keyboard;
public StateManager stateManager; public StateManager stateManager;
public Motherboard motherboard; 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.") @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 boolean enableStateManager;
public final SoundMixer mixer;
/** /**
* Creates a new instance of Computer * Creates a new instance of Computer
*/ */
public Computer() { public Computer() {
keyboard = new Keyboard(this); keyboard = new Keyboard(this);
mixer = new SoundMixer(this);
romLoaded = false;
} }
public RAM getMemory() { public RAM getMemory() {
@ -100,12 +104,14 @@ public abstract class Computer implements Reconfigurable {
public void loadRom(String path) throws IOException { public void loadRom(String path) throws IOException {
memory.loadRom(path); memory.loadRom(path);
romLoaded = true;
} }
public void deactivate() { public void deactivate() {
cpu.suspend(); cpu.suspend();
motherboard.suspend(); motherboard.suspend();
video.suspend(); video.suspend();
mixer.detach();
} }
@InvokableAction( @InvokableAction(
@ -116,7 +122,18 @@ public abstract class Computer implements Reconfigurable {
consumeKeyEvent = true, consumeKeyEvent = true,
defaultKeyMapping = {"Ctrl+Shift+Backspace","Ctrl+Shift+Delete"}) defaultKeyMapping = {"Ctrl+Shift+Backspace","Ctrl+Shift+Delete"})
public void invokeColdStart() { 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(); public abstract void coldStart();
@ -155,6 +172,7 @@ public abstract class Computer implements Reconfigurable {
@Override @Override
public void reconfigure() { public void reconfigure() {
mixer.reconfigure();
if (enableStateManager) { if (enableStateManager) {
stateManager = StateManager.getInstance(this); stateManager = StateManager.getInstance(this);
} else { } else {

View File

@ -42,7 +42,6 @@ public class Motherboard extends TimedDevice {
@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;
public SoundMixer mixer;
void vblankEnd() { void vblankEnd() {
SoftSwitches.VBL.getSwitch().setState(true); SoftSwitches.VBL.getSwitch().setState(true);
@ -59,7 +58,6 @@ public class Motherboard extends TimedDevice {
*/ */
public Motherboard(Computer computer) { public Motherboard(Computer computer) {
super(computer); super(computer);
mixer = new SoundMixer(computer);
} }
@Override @Override
@ -112,21 +110,19 @@ public class Motherboard extends TimedDevice {
accelorationRequestors.clear(); accelorationRequestors.clear();
super.reconfigure(); super.reconfigure();
// Now create devices as needed, e.g. sound // Now create devices as needed, e.g. sound
miscDevices.add(mixer);
mixer.reconfigure();
if (enableSpeaker) { if (enableSpeaker) {
try { try {
if (speaker == null) { if (speaker == null) {
speaker = new Speaker(computer); 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.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();
@ -167,7 +163,9 @@ public class Motherboard extends TimedDevice {
synchronized (resume) { synchronized (resume) {
resume.clear(); resume.clear();
for (Optional<Card> c : computer.getMemory().getAllCards()) { for (Optional<Card> c : computer.getMemory().getAllCards()) {
if (!c.isPresent()) continue; if (!c.isPresent()) {
continue;
}
if (!c.get().suspendWithCPU() || !c.get().isRunning()) { if (!c.get().suspendWithCPU() || !c.get().isRunning()) {
continue; continue;
} }

View File

@ -44,7 +44,7 @@ import javax.sound.sampled.SourceDataLine;
* how to reuse active lines if needed. It is possible that this class might be * 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. * 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 { public class SoundMixer extends Device {
@ -103,19 +103,25 @@ public class SoundMixer extends Device {
@Override @Override
public synchronized void reconfigure() { public synchronized void reconfigure() {
detach(); if (isConfigDifferent()) {
try { detach();
initMixer(); try {
if (lineAvailable) { initMixer();
initAudio(); if (lineAvailable) {
} else { initAudio();
System.out.println("Sound not stared: Line not available"); } 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) { attach();
System.out.println("Unable to start sound");
Logger.getLogger(SoundMixer.class.getName()).log(Level.SEVERE, null, ex);
} }
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 * available
*/ */
private void initAudio() throws LineUnavailableException { private void initAudio() throws LineUnavailableException {
af = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, RATE, BITS, 2, BITS / 4, RATE, true); af = getAudioFormat();
// af = new AudioFormat(RATE, BITS, 2, true, true);
DataLine.Info dli = new DataLine.Info(SourceDataLine.class, af); DataLine.Info dli = new DataLine.Info(SourceDataLine.class, af);
lineAvailable = AudioSystem.isLineSupported(dli); lineAvailable = AudioSystem.isLineSupported(dli);
} }
@ -152,8 +157,10 @@ 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.flush(); if (sdl.isRunning()) {
sdl.stop(); sdl.flush();
sdl.stop();
}
availableLines.add(sdl); availableLines.add(sdl);
} }
} }
@ -213,7 +220,9 @@ public class SoundMixer extends Device {
}); });
if (theMixer != null) { if (theMixer != null) {
for (Line l : theMixer.getSourceLines()) { for (Line l : theMixer.getSourceLines()) {
l.close(); // if (l.isOpen()) {
// l.close();
// }
} }
} }
availableLines.clear(); availableLines.clear();
@ -241,9 +250,24 @@ public class SoundMixer extends Device {
} }
} }
theMixer = AudioSystem.getMixer(selected); theMixer = AudioSystem.getMixer(selected);
for (Line l : theMixer.getSourceLines()) { // for (Line l : theMixer.getSourceLines()) {
l.close(); // l.close();
} // }
lineAvailable = true; 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;
}
} }

View File

@ -30,12 +30,14 @@ import jace.core.SoundMixer;
import static jace.core.Utility.*; import static jace.core.Utility.*;
import jace.hardware.mockingboard.PSG; import jace.hardware.mockingboard.PSG;
import jace.hardware.mockingboard.R6522; import jace.hardware.mockingboard.R6522;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javafx.application.Platform;
import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.SourceDataLine;
@ -50,7 +52,6 @@ import javax.sound.sampled.SourceDataLine;
public class CardMockingboard extends Card implements Runnable { public class CardMockingboard extends Card implements Runnable {
// If true, emulation will cover 4 AY chips. Otherwise, only 2 AY chips // 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", @ConfigurableField(name = "Volume", shortName = "vol",
category = "Sound", category = "Sound",
description = "Mockingboard volume, 100=max, 0=silent") description = "Mockingboard volume, 100=max, 0=silent")
@ -93,27 +94,33 @@ public class CardMockingboard extends Card implements Runnable {
//don't ask... //don't ask...
final int j = i; final int j = i;
controllers[i] = new R6522(computer) { controllers[i] = new R6522(computer) {
int controller = j;
@Override @Override
public void sendOutputA(int value) { public void sendOutputA(int value) {
if (activeChip != null) { chips[j].setBus(value);
activeChip.setBus(value); if (phasorMode) {
} else { chips[j+2].setBus(value);
System.out.println("No active AY chip!");
} }
} }
@Override @Override
public void sendOutputB(int value) { public void sendOutputB(int value) {
if (activeChip != null) { if (phasorMode) {
activeChip.setControl(value & 0x07); 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 { } else {
System.out.println("No active AY chip!"); chips[j].setControl(value & 0x07);
} }
} }
@Override @Override
public int receiveOutputA() { public int receiveOutputA() {
return activeChip == null ? 0 : activeChip.bus; return chips[j] == null ? 0 : chips[j].bus;
} }
@Override @Override
@ -140,22 +147,19 @@ public class CardMockingboard extends Card implements Runnable {
} }
} }
RAMListener mainListener = null; RAMListener mainListener = null;
PSG activeChip = null;
@Override @Override
protected void handleFirmwareAccess(int register, TYPE type, int value, RAMEvent e) { 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()); // System.out.println(e.getType().toString() + " event to mockingboard register "+Integer.toHexString(register)+", value "+e.getNewValue());
activeChip = null;
resume(); resume();
int chip = 0; int chip = 0;
for (PSG psg : chips) { for (PSG psg : chips) {
if (psg.getBaseReg() == (register & 0x0f0)) { if (psg.getBaseReg() == (register & 0x0f0)) {
activeChip = psg;
break; break;
} }
chip++; chip++;
} }
if (activeChip == null) { if (chip >= 2) {
System.err.println("Could not determine which PSG to communicate to"); System.err.println("Could not determine which PSG to communicate to");
e.setNewValue(computer.getVideo().getFloatingBus()); e.setNewValue(computer.getVideo().getFloatingBus());
return; return;
@ -213,7 +217,7 @@ public class CardMockingboard extends Card implements Runnable {
boolean restart = suspend(); boolean restart = suspend();
initPSG(); initPSG();
for (PSG chip : chips) { for (PSG chip : chips) {
chip.setRate(CLOCK_SPEED, SAMPLE_RATE); chip.setRate(phasorMode ? CLOCK_SPEED*2 : CLOCK_SPEED, SAMPLE_RATE);
chip.reset(); chip.reset();
} }
super.reconfigure(); super.reconfigure();
@ -260,7 +264,7 @@ public class CardMockingboard extends Card implements Runnable {
} }
Thread playbackThread = null; Thread playbackThread = null;
boolean pause = false; boolean pause = false;
@Override @Override
public void resume() { public void resume() {
pause = false; pause = false;
@ -308,7 +312,7 @@ public class CardMockingboard extends Card implements Runnable {
*/ */
public void run() { public void run() {
try { try {
SourceDataLine out = computer.getMotherboard().mixer.getLine(this); SourceDataLine out = computer.mixer.getLine(this);
int[] leftBuffer = new int[BUFFER_LENGTH]; int[] leftBuffer = new int[BUFFER_LENGTH];
int[] rightBuffer = new int[BUFFER_LENGTH]; int[] rightBuffer = new int[BUFFER_LENGTH];
int frameSize = out.getFormat().getFrameSize(); int frameSize = out.getFormat().getFrameSize();
@ -319,6 +323,7 @@ public class CardMockingboard extends Card implements Runnable {
ticksBeteenPlayback = (int) ((Motherboard.SPEED * BUFFER_LENGTH) / SAMPLE_RATE); ticksBeteenPlayback = (int) ((Motherboard.SPEED * BUFFER_LENGTH) / SAMPLE_RATE);
ticksSinceLastPlayback = 0; ticksSinceLastPlayback = 0;
int zeroSamples = 0; int zeroSamples = 0;
setRun(true);
while (isRunning()) { while (isRunning()) {
computer.getMotherboard().requestSpeed(this); computer.getMotherboard().requestSpeed(this);
playSound(leftBuffer, rightBuffer); playSound(leftBuffer, rightBuffer);
@ -385,15 +390,21 @@ public class CardMockingboard extends Card implements Runnable {
} finally { } finally {
computer.getMotherboard().cancelSpeedRequest(this); computer.getMotherboard().cancelSpeedRequest(this);
System.out.println("Mockingboard playback stopped"); System.out.println("Mockingboard playback stopped");
computer.getMotherboard().mixer.returnLine(this); computer.mixer.returnLine(this);
} }
} }
private void initPSG() { private void initPSG() {
int max = phasorMode ? 4 : 2; if (phasorMode) {
chips = new PSG[max]; chips = new PSG[4];
for (int i = 0; i < max; i++) { chips[0] = new PSG(0x10, CLOCK_SPEED*2, SAMPLE_RATE, "AY1", 8);
chips[i] = new PSG(AY_ADDRESSES[i], CLOCK_SPEED, SAMPLE_RATE, "AY" + i); 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);
} }
} }

View File

@ -106,9 +106,11 @@ public class PSG {
int selectedReg; int selectedReg;
String name; String name;
Map<Reg, Integer> regValues; Map<Reg, Integer> 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.name = name;
this.mask = DDR_Mask;
baseReg = base; baseReg = base;
channels = new ArrayList<>(); channels = new ArrayList<>();
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {