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;
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

View File

@ -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 {

View File

@ -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<Card> c : computer.getMemory().getAllCards()) {
if (!c.isPresent()) continue;
if (!c.isPresent()) {
continue;
}
if (!c.get().suspendWithCPU() || !c.get().isRunning()) {
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
* 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;
}
}

View File

@ -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);
}
}

View File

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