mirror of
https://github.com/badvision/lawless-legends.git
synced 2025-03-01 03:30:04 +00:00
Merge branch 'master' of github.com:badvision/lawless-legends
This commit is contained in:
commit
9a9464da3c
@ -86,7 +86,7 @@ public class EmulatorUILogic implements Reconfigurable {
|
||||
category = "General",
|
||||
name = "Speed Setting"
|
||||
)
|
||||
public int speedSetting = 2;
|
||||
public int speedSetting = 3;
|
||||
|
||||
@ConfigurableField(
|
||||
category = "General",
|
||||
@ -560,5 +560,6 @@ public class EmulatorUILogic implements Reconfigurable {
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
LawlessLegends.getApplication().controller.setSpeed(speedSetting);
|
||||
}
|
||||
}
|
||||
|
@ -6,12 +6,10 @@
|
||||
package jace;
|
||||
|
||||
import com.sun.glass.ui.Application;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.core.Card;
|
||||
import jace.core.Computer;
|
||||
import jace.core.Motherboard;
|
||||
import jace.core.Utility;
|
||||
import jace.lawless.LawlessComputer;
|
||||
import jace.library.MediaCache;
|
||||
import jace.library.MediaConsumer;
|
||||
import jace.library.MediaConsumerParent;
|
||||
@ -109,6 +107,7 @@ public class JaceUIController {
|
||||
assert stackPane != null : "fx:id=\"stackPane\" was not injected: check your FXML file 'JaceUI.fxml'.";
|
||||
assert notificationBox != null : "fx:id=\"notificationBox\" was not injected: check your FXML file 'JaceUI.fxml'.";
|
||||
assert appleScreen != null : "fx:id=\"appleScreen\" was not injected: check your FXML file 'JaceUI.fxml'.";
|
||||
speedSlider.setValue(1.0);
|
||||
controlOverlay.setVisible(false);
|
||||
menuButtonPane.setVisible(false);
|
||||
controlOverlay.setFocusTraversable(false);
|
||||
@ -183,7 +182,7 @@ public class JaceUIController {
|
||||
}
|
||||
}
|
||||
|
||||
private double convertSpeedToRatio(Double setting) {
|
||||
protected double convertSpeedToRatio(Double setting) {
|
||||
if (setting < 1.0) {
|
||||
return 0.5;
|
||||
} else if (setting == 1.0) {
|
||||
@ -209,7 +208,6 @@ public class JaceUIController {
|
||||
rootPane.setOnKeyReleased(keyboardHandler);
|
||||
rootPane.setFocusTraversable(true);
|
||||
}
|
||||
speedSlider.setValue(1.0);
|
||||
speedSlider.setMinorTickCount(0);
|
||||
speedSlider.setMajorTickUnit(1);
|
||||
speedSlider.setLabelFormatter(new StringConverter<Double>() {
|
||||
@ -253,9 +251,12 @@ public class JaceUIController {
|
||||
}
|
||||
}
|
||||
|
||||
private void setSpeed(double speed) {
|
||||
protected void setSpeed(double speed) {
|
||||
Emulator.logic.speedSetting = (int) speed;
|
||||
double speedRatio = convertSpeedToRatio(speed);
|
||||
if (speedSlider.getValue() != speed) {
|
||||
Platform.runLater(()->speedSlider.setValue(speed));
|
||||
}
|
||||
if (speedRatio > 100.0) {
|
||||
Emulator.computer.getMotherboard().setMaxSpeed(true);
|
||||
Motherboard.cpuPerClock = 3;
|
||||
|
@ -38,8 +38,8 @@ import jace.hardware.CardExt80Col;
|
||||
import jace.hardware.ConsoleProbe;
|
||||
import jace.hardware.Joystick;
|
||||
import jace.hardware.NoSlotClock;
|
||||
import jace.hardware.ZipWarpAccelerator;
|
||||
import jace.hardware.massStorage.CardMassStorage;
|
||||
import jace.lawless.LawlessComputer;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
@ -96,6 +96,8 @@ public class Apple2e extends Computer {
|
||||
public boolean joy2enabled = false;
|
||||
@ConfigurableField(name = "No-Slot Clock Enabled", shortName = "clock", description = "If checked, no-slot clock will be enabled", enablesDevice = true)
|
||||
public boolean clockEnabled = true;
|
||||
@ConfigurableField(name = "Accelerator Enabled", shortName = "zip", description = "If checked, add support for Zip/Transwarp", enablesDevice = true)
|
||||
public boolean acceleratorEnabled = true;
|
||||
|
||||
public Joystick joystick1;
|
||||
public Joystick joystick2;
|
||||
@ -103,6 +105,7 @@ public class Apple2e extends Computer {
|
||||
public ClassSelection cheatEngine = new ClassSelection(Cheats.class, null);
|
||||
public Cheats activeCheatEngine = null;
|
||||
public NoSlotClock clock;
|
||||
public ZipWarpAccelerator accelerator;
|
||||
|
||||
/**
|
||||
* Creates a new instance of Apple2e
|
||||
@ -235,6 +238,17 @@ public class Apple2e extends Computer {
|
||||
currentMemory.reconfigure();
|
||||
|
||||
if (motherboard != null) {
|
||||
if (accelerator == null) {
|
||||
accelerator = new ZipWarpAccelerator(this);
|
||||
}
|
||||
if (acceleratorEnabled) {
|
||||
accelerator.attach();
|
||||
motherboard.miscDevices.add(accelerator);
|
||||
} else {
|
||||
accelerator.detach();
|
||||
motherboard.miscDevices.remove(accelerator);
|
||||
}
|
||||
|
||||
if (joy1enabled) {
|
||||
if (joystick1 == null) {
|
||||
joystick1 = new Joystick(0, this);
|
||||
|
@ -21,9 +21,9 @@ package jace.core;
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.config.Reconfigurable;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* RAM is a 64K address space of paged memory. It also manages sets of memory
|
||||
@ -37,9 +37,9 @@ public abstract class RAM implements Reconfigurable {
|
||||
|
||||
public PagedMemory activeRead;
|
||||
public PagedMemory activeWrite;
|
||||
public List<RAMListener> listeners;
|
||||
public List<RAMListener>[] listenerMap;
|
||||
public List<RAMListener>[] ioListenerMap;
|
||||
public Set<RAMListener> listeners;
|
||||
public Set<RAMListener>[] listenerMap;
|
||||
public Set<RAMListener>[] ioListenerMap;
|
||||
public Optional<Card>[] cards;
|
||||
// card 0 = 80 column card firmware / system rom
|
||||
public int activeSlot = 0;
|
||||
@ -52,7 +52,7 @@ public abstract class RAM implements Reconfigurable {
|
||||
*/
|
||||
public RAM(Computer computer) {
|
||||
this.computer = computer;
|
||||
listeners = new ArrayList<>();
|
||||
listeners = new HashSet<>();
|
||||
cards = new Optional[8];
|
||||
for (int i = 0; i < 8; i++) {
|
||||
cards[i] = Optional.empty();
|
||||
@ -79,7 +79,7 @@ public abstract class RAM implements Reconfigurable {
|
||||
|
||||
public Optional<Card> getCard(int slot) {
|
||||
if (slot >= 1 && slot <= 7) {
|
||||
return cards[slot];
|
||||
return cards[slot].flatMap(card->card == null ? Optional.empty() : Optional.of(card));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
@ -158,24 +158,20 @@ public abstract class RAM implements Reconfigurable {
|
||||
private void mapListener(RAMListener l, int address) {
|
||||
if ((address & 0x0FF00) == 0x0C000) {
|
||||
int index = address & 0x0FF;
|
||||
List<RAMListener> ioListeners = ioListenerMap[index];
|
||||
Set<RAMListener> ioListeners = ioListenerMap[index];
|
||||
if (ioListeners == null) {
|
||||
ioListeners = new ArrayList<>();
|
||||
ioListeners = new HashSet<>();
|
||||
ioListenerMap[index] = ioListeners;
|
||||
}
|
||||
if (!ioListeners.contains(l)) {
|
||||
ioListeners.add(l);
|
||||
}
|
||||
ioListeners.add(l);
|
||||
} else {
|
||||
int index = address >> 8;
|
||||
List<RAMListener> otherListeners = listenerMap[index];
|
||||
Set<RAMListener> otherListeners = listenerMap[index];
|
||||
if (otherListeners == null) {
|
||||
otherListeners = new ArrayList<>();
|
||||
otherListeners = new HashSet<>();
|
||||
listenerMap[index] = otherListeners;
|
||||
}
|
||||
if (!otherListeners.contains(l)) {
|
||||
otherListeners.add(l);
|
||||
}
|
||||
otherListeners.add(l);
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,8 +192,8 @@ public abstract class RAM implements Reconfigurable {
|
||||
}
|
||||
|
||||
private void refreshListenerMap() {
|
||||
listenerMap = new ArrayList[256];
|
||||
ioListenerMap = new ArrayList[256];
|
||||
listenerMap = new Set[256];
|
||||
ioListenerMap = new Set[256];
|
||||
listeners.stream().forEach((l) -> {
|
||||
addListenerRange(l);
|
||||
});
|
||||
@ -299,7 +295,7 @@ public abstract class RAM implements Reconfigurable {
|
||||
}
|
||||
|
||||
public byte callListener(RAMEvent.TYPE t, int address, int oldValue, int newValue, boolean requireSyncronization) {
|
||||
List<RAMListener> activeListeners;
|
||||
Set<RAMListener> activeListeners;
|
||||
if (requireSyncronization) {
|
||||
computer.getCpu().suspend();
|
||||
}
|
||||
|
@ -119,15 +119,13 @@ public abstract class RAMListener implements RAMEvent.RAMEventHandler {
|
||||
|
||||
public boolean isRelevant(RAMEvent e) {
|
||||
// Skip event if it's not the right type
|
||||
if (type != TYPE.ANY && e.getType() != TYPE.ANY) {
|
||||
if ((type != e.getType())) {
|
||||
if (type == TYPE.READ) {
|
||||
if (!e.getType().isRead()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (type != TYPE.ANY && type != e.getType()) {
|
||||
if (e.getType() != TYPE.ANY && type == TYPE.READ) {
|
||||
if (!e.getType().isRead()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Skip event if it's not in the scope we care about
|
||||
|
@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright 2018 org.badvision.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.core.Computer;
|
||||
import jace.core.Device;
|
||||
import jace.core.Motherboard;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
|
||||
/**
|
||||
* Implements a basic hardware accelerator that is able to adjust the speed of the emulator
|
||||
*/
|
||||
public class ZipWarpAccelerator extends Device {
|
||||
@ConfigurableField(category = "debug", name = "Debug messages")
|
||||
public boolean debugMessagesEnabled = false;
|
||||
|
||||
public static final int ENABLE_ADDR = 0x0c05a;
|
||||
public static final int MAX_SPEED = 0x0c05b;
|
||||
public static final int REGISTERS = 0x0c05c;
|
||||
public static final int SET_SPEED = 0x0c05d;
|
||||
public static final int UNLOCK_VAL = 0x05A;
|
||||
public static final int LOCK_VAL = 0x0A5;
|
||||
public static final double UNLOCK_PENALTY_PER_TICK = 0.19;
|
||||
public static final double UNLOCK_MIN = 4.0;
|
||||
|
||||
public static final int TRANSWARP = 0x0c074;
|
||||
public static final int TRANSWARP_ON = 1; // Any other value written disables acceleration
|
||||
|
||||
boolean zipLocked = true;
|
||||
double zipUnlockCount = 0;
|
||||
int zipRegisters = 0;
|
||||
int speedValue = 0;
|
||||
|
||||
RAMListener zipListener;
|
||||
RAMListener transwarpListener;
|
||||
|
||||
public ZipWarpAccelerator(Computer computer) {
|
||||
super(computer);
|
||||
zipListener = computer.memory.observe(RAMEvent.TYPE.ANY, ENABLE_ADDR, SET_SPEED, this::handleZipChipEvent);
|
||||
transwarpListener = computer.memory.observe(RAMEvent.TYPE.ANY, TRANSWARP, this::handleTranswarpEvent);
|
||||
}
|
||||
|
||||
private void handleZipChipEvent(RAMEvent e) {
|
||||
boolean isWrite = e.getType() == RAMEvent.TYPE.WRITE;
|
||||
if (ENABLE_ADDR == e.getAddress() && isWrite) {
|
||||
if (e.getNewValue() == UNLOCK_VAL) {
|
||||
zipUnlockCount = Math.ceil(zipUnlockCount) + 1.0;
|
||||
if (debugMessagesEnabled) {
|
||||
System.out.println("Unlock sequence detected, new lock value at " + zipUnlockCount + " of " + UNLOCK_MIN);
|
||||
}
|
||||
if (zipUnlockCount >= UNLOCK_MIN) {
|
||||
zipLocked = false;
|
||||
if (debugMessagesEnabled) {
|
||||
System.out.println("Zip unlocked!");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
zipLocked = true;
|
||||
if (debugMessagesEnabled) {
|
||||
System.out.println("Zip locked!");
|
||||
}
|
||||
zipUnlockCount = 0;
|
||||
if ((e.getNewValue() & 0x0ff) != LOCK_VAL) {
|
||||
if (debugMessagesEnabled) {
|
||||
System.out.println("Warp disabled.");
|
||||
}
|
||||
setSpeed(SPEED._1);
|
||||
}
|
||||
}
|
||||
} else if (!zipLocked && isWrite) {
|
||||
switch (e.getAddress()) {
|
||||
case MAX_SPEED:
|
||||
setSpeed(SPEED.MAX);
|
||||
if (debugMessagesEnabled) {
|
||||
System.out.println("MAXIMUM WARP!");
|
||||
}
|
||||
break;
|
||||
case SET_SPEED:
|
||||
SPEED s = lookupSpeedSetting(e.getNewValue());
|
||||
setSpeed(s);
|
||||
if (debugMessagesEnabled) {
|
||||
System.out.println("Set speed to " + s.ratio);
|
||||
}
|
||||
break;
|
||||
case REGISTERS:
|
||||
zipRegisters = e.getNewValue();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (!zipLocked && e.getAddress() == REGISTERS) {
|
||||
e.setNewValue(zipRegisters);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTranswarpEvent(RAMEvent e) {
|
||||
if (e.getType().isRead()) {
|
||||
e.setNewValue(speedValue);
|
||||
} else {
|
||||
if (e.getNewValue() == TRANSWARP_ON) {
|
||||
setSpeed(SPEED.MAX);
|
||||
if (debugMessagesEnabled) {
|
||||
System.out.println("MAXIMUM WARP!");
|
||||
}
|
||||
} else {
|
||||
setSpeed(SPEED._1);
|
||||
if (debugMessagesEnabled) {
|
||||
System.out.println("Warp disabled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDeviceName() {
|
||||
return "ZipChip Accelerator";
|
||||
}
|
||||
|
||||
public static enum SPEED {
|
||||
MAX(4.0, 0b000000000, 0b011111100),
|
||||
_2_667(2.6667, 0b000000100, 0b011111100),
|
||||
_3(3.0, 0b000001000, 0b011111000),
|
||||
_3_2(3.2, 0b000010000, 0b011110000),
|
||||
_3_333(3.333, 0b000100000, 0b011100000),
|
||||
_2(2.0, 0b001000000, 0b011111100),
|
||||
_1_333(1.333, 0b001000100, 0b011111100),
|
||||
_1_5(1.5, 0b001001000, 0b011111000),
|
||||
_1_6(1.6, 0b001010000, 0b011110000),
|
||||
_1_667(1.6667, 0b001100000, 0b011100000),
|
||||
_1b(1.0, 0b010000000, 0b011111100),
|
||||
_0_667(0.6667, 0b010000100, 0b011111100),
|
||||
_0_75(0.75, 0b010001000, 0b011111000),
|
||||
_0_8(0.8, 0b010010000, 0b011110000),
|
||||
_0_833(0.833, 0b010100000, 0b011100000),
|
||||
_1_33b(1.333, 0b011000000, 0b011111100),
|
||||
_0_889(0.8889, 0b011000100, 0b011111100),
|
||||
_1(1.0, 0b011001000, 0b011111000),
|
||||
_1_067(1.0667, 0b011010000, 0b011110000),
|
||||
_1_111(1.111, 0b011100000, 0b011100000);
|
||||
double ratio;
|
||||
int val;
|
||||
int mask;
|
||||
boolean max;
|
||||
|
||||
SPEED(double speed, int val, int mask) {
|
||||
this.ratio = speed;
|
||||
this.val = val;
|
||||
this.mask = mask;
|
||||
this.max = speed >= 4.0;
|
||||
}
|
||||
}
|
||||
|
||||
private SPEED lookupSpeedSetting(int v) {
|
||||
for (SPEED s : SPEED.values()) {
|
||||
if ((v & s.mask) == s.val) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return SPEED._1;
|
||||
}
|
||||
|
||||
private void setSpeed(SPEED speed) {
|
||||
speedValue = speed.val;
|
||||
if (speed.max) {
|
||||
Emulator.computer.getMotherboard().setMaxSpeed(true);
|
||||
Motherboard.cpuPerClock = 3;
|
||||
} else {
|
||||
Emulator.computer.getMotherboard().setMaxSpeed(false);
|
||||
Emulator.computer.getMotherboard().setSpeedInPercentage((int) (speed.ratio * 100));
|
||||
Motherboard.cpuPerClock = 1;
|
||||
}
|
||||
Emulator.computer.getMotherboard().reconfigure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
if (zipUnlockCount > 0) {
|
||||
zipUnlockCount -= UNLOCK_PENALTY_PER_TICK;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
computer.memory.addListener(zipListener);
|
||||
computer.memory.addListener(transwarpListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
super.detach();
|
||||
computer.memory.removeListener(zipListener);
|
||||
computer.memory.removeListener(transwarpListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "zip";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
zipUnlockCount = 0;
|
||||
zipLocked = true;
|
||||
}
|
||||
|
||||
}
|
304
Platform/Apple/virtual/src/plasma/music.pla → Platform/Apple/virtual/src/plasma/sndseq.pla
Normal file → Executable file
304
Platform/Apple/virtual/src/plasma/music.pla → Platform/Apple/virtual/src/plasma/sndseq.pla
Normal file → Executable file
@ -1,9 +1,10 @@
|
||||
include "inc/cmdsys.plh"
|
||||
include "inc/fileio.plh"
|
||||
include "inc/args.plh"
|
||||
//
|
||||
// Usage is documented following the source in this file...
|
||||
//
|
||||
const rndseed = $004E
|
||||
const FALSE = 0
|
||||
const TRUE = !FALSE
|
||||
const LSB = 0
|
||||
const MSB = 1
|
||||
const MB_ARPEGGIO = 4 // In 16ths of a second
|
||||
@ -47,6 +48,10 @@ struc t_PSG
|
||||
byte ENVSHAPE // Envelope Shape
|
||||
end
|
||||
//
|
||||
// Apple III hardware constants.
|
||||
//
|
||||
const ENV_REG = $FFDF
|
||||
//
|
||||
// Sequence event
|
||||
//
|
||||
struc t_event
|
||||
@ -59,11 +64,20 @@ end
|
||||
//
|
||||
predef musicPlay(track, rept)#0
|
||||
predef musicStop#0
|
||||
predef backgroundProc#0
|
||||
predef spkrSequence(yield, func)#0
|
||||
predef a2spkrTone(pitch, duration)#0
|
||||
predef a2spkrPWM(sample, speed, len)#0
|
||||
predef a2keypressed
|
||||
//
|
||||
// Static sequencer values
|
||||
//
|
||||
word seqTrack, seqEvent, seqTime, eventTime, updateTime, musicSequence
|
||||
export word musicSequence = @spkrSequence
|
||||
export word spkrTone = @a2spkrTone
|
||||
export word spkrPWM = @a2spkrPWM
|
||||
word keypressed = @a2keypressed
|
||||
|
||||
word instr[] // Overlay with other variables
|
||||
word seqTrack, seqEvent, seqTime, eventTime, updateTime
|
||||
byte numNotes, seqRepeat
|
||||
byte indexA[2], indexB[2], indexC[2]
|
||||
byte noteA[2], noteB[2], noteC[2]
|
||||
@ -75,8 +89,8 @@ word periods[2] = @periods1, @periods2
|
||||
// MockingBoard data.
|
||||
//
|
||||
word[] mbVIAs // Treat this as an array of VIA ptrs
|
||||
word mbVIA1 = -1 // Init to "discover MockingBoard flag" value
|
||||
word mbVIA2 = 0
|
||||
word mbVIA1, mbVIA2
|
||||
word mbSlot = -1
|
||||
//
|
||||
// Octave basis frequency periods (starting at MIDI note #12)
|
||||
// Notes will be encoded as basis note (LSNibble) and octave (MSNibble))
|
||||
@ -84,20 +98,6 @@ word mbVIA2 = 0
|
||||
word[] spkrOctave0 // Overlay and scale mbOctave0 for speaker version
|
||||
word[12] mbOctave0 = 3900, 3681, 3474, 3279, 3095, 2922, 2758, 2603, 2457, 2319, 2189, 2066
|
||||
word[5] arpeggioDuration = DUR16TH, DUR16TH, DUR16TH/2, DUR16TH/3, DUR16TH/4
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// These are utility sequences/routines needed to test the music sequencer code.
|
||||
//
|
||||
asm toneTrack
|
||||
include "test.seq"
|
||||
end
|
||||
asm putc(ch)#0
|
||||
LDA ESTKL,X
|
||||
INX
|
||||
ORA #$80
|
||||
JMP $FDED
|
||||
end
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Emulators are broken - they only activate the MockingBoard's 6522 Timer1
|
||||
// functionality when interrupts are enabled. This music sequencer is run
|
||||
@ -106,6 +106,9 @@ end
|
||||
// disabled. NO INTERRUPTS ARE HANDLED WHEN PLAYING MUSIC! The previous state
|
||||
// is restored between playing sequences.
|
||||
//
|
||||
asm vmincs
|
||||
!SOURCE "vmsrc/plvmzp.inc"
|
||||
end
|
||||
asm getStatusReg#1
|
||||
PHP
|
||||
PLA
|
||||
@ -210,10 +213,35 @@ asm psgWrite(pVIA, reg, val)#0
|
||||
INX
|
||||
RTS
|
||||
end
|
||||
asm viaCheck(pVIA)#1
|
||||
PHP
|
||||
SEI
|
||||
LDA ESTKL,X
|
||||
STA TMPL
|
||||
LDA ESTKH,X
|
||||
STA TMPH
|
||||
STX ESP
|
||||
LDX #$80
|
||||
LDY #$04
|
||||
SEC
|
||||
- LDA (TMP),Y
|
||||
BMI +
|
||||
DEX
|
||||
BNE -
|
||||
TXA ; TIMEOUT
|
||||
BEQ ++
|
||||
+ SBC (TMP),Y
|
||||
++ LDX ESP
|
||||
STA ESTKL,X
|
||||
LDA #$00
|
||||
STA ESTKH,X
|
||||
PLP
|
||||
RTS
|
||||
end
|
||||
//
|
||||
// Apple II speaker tone generator routines
|
||||
//
|
||||
export asm spkrTone(pitch, duration)#0
|
||||
asm a2spkrTone(pitch, duration)#0
|
||||
STX ESP
|
||||
LDY ESTKH,X
|
||||
LDA ESTKL,X
|
||||
@ -287,7 +315,7 @@ TONEXIT PLP
|
||||
INX
|
||||
RTS
|
||||
end
|
||||
export asm spkrPWM(sample, speed, len)#0
|
||||
asm a2spkrPWM(sample, speed, len)#0
|
||||
STX ESP
|
||||
LDY ESTKH,X
|
||||
LDA ESTKL,X
|
||||
@ -327,18 +355,60 @@ export asm spkrPWM(sample, speed, len)#0
|
||||
INX
|
||||
RTS
|
||||
end
|
||||
asm a2keypressed
|
||||
INC $004E ; rndseed
|
||||
BNE +
|
||||
INC $004F
|
||||
+ DEX
|
||||
LDY #$00
|
||||
BIT $C000
|
||||
BPL +
|
||||
DEY
|
||||
+ STY ESTKL,X
|
||||
STY ESTKH,X
|
||||
RTS
|
||||
end
|
||||
def a3keypressed
|
||||
byte count
|
||||
byte params[5]
|
||||
|
||||
params.0 = 3
|
||||
params.1 = cmdsys.devcons
|
||||
params.2 = 5
|
||||
params:3 = @count
|
||||
syscall($82, @params)
|
||||
return count
|
||||
end
|
||||
def a3spkrTone(pitch, duration)#0
|
||||
byte env
|
||||
|
||||
env = ^ENV_REG
|
||||
^ENV_REG = env | $C0
|
||||
a2spkrTone(pitch, duration)
|
||||
^ENV_REG = env
|
||||
end
|
||||
def a3spkrPWM(sample, speed, len)#0
|
||||
byte env
|
||||
|
||||
env = ^ENV_REG
|
||||
^ENV_REG = env | $C0
|
||||
a2spkrPWM(sample, speed, len)
|
||||
^ENV_REG = env
|
||||
end
|
||||
//
|
||||
// Search slots for MockingBoard
|
||||
//
|
||||
def mbTicklePSG(pVIA)
|
||||
pVIA->IER = $7F // Mask all interrupts
|
||||
pVIA->ACR = $00 // Stop T1 countdown
|
||||
pVIA->DDRB = $FF // Output enable port A and B
|
||||
pVIA->DDRA = $FF
|
||||
pVIA->IORA = $00 // Reset MockingBoard
|
||||
if pVIA->IORA == $00
|
||||
//puts("VIA address: $"); puth(pVIA); puts(" Timer Diff = "); puti(viaCheck(pVIA)); putln
|
||||
if viaCheck(pVIA) == 8 and viaCheck(pVIA) == 8 // Check twice
|
||||
pVIA->IER = $7F // Mask all interrupts
|
||||
//pVIA->ACR = $00 // Stop T1 countdown
|
||||
pVIA->DDRB = $FF // Output enable port A and B
|
||||
pVIA->DDRA = $FF
|
||||
pVIA->IORA = $00 // Reset MockingBoard
|
||||
//if pVIA->IORA == $00
|
||||
pVIA->IORA = $04 // Inactive MockingBoard control lines
|
||||
if pVIA->IORA == $04
|
||||
//if pVIA->IORA == $04
|
||||
//
|
||||
// At least we know we have some sort of R/W in the ROM
|
||||
// address space. Most likely a MockingBoard or John Bell
|
||||
@ -349,30 +419,32 @@ def mbTicklePSG(pVIA)
|
||||
//if mbReadP(pVIA, 2) == $7E and mbReadP(pVIA, 3) == $0A
|
||||
return pVIA
|
||||
//fin
|
||||
fin
|
||||
//fin
|
||||
fin
|
||||
return 0
|
||||
end
|
||||
def mbSearch(slot)
|
||||
if slot
|
||||
mbVIA1 = mbTicklePSG($C000 + (slot << 8))
|
||||
if mbVIA1
|
||||
mbVIA2 = mbTicklePSG(mbVIA1 + $80)
|
||||
return slot
|
||||
fin
|
||||
else
|
||||
for slot = 1 to 7
|
||||
if slot == 3 or slot == 6
|
||||
continue
|
||||
fin
|
||||
if slot >= 0 and slot <= 7
|
||||
if slot
|
||||
mbVIA1 = mbTicklePSG($C000 + (slot << 8))
|
||||
if mbVIA1
|
||||
mbVIA2 = mbTicklePSG(mbVIA1 + $80)
|
||||
return slot
|
||||
fin
|
||||
next
|
||||
else
|
||||
for slot = 1 to 7
|
||||
if slot == 3 or slot == 6
|
||||
continue
|
||||
fin
|
||||
mbVIA1 = mbTicklePSG($C000 + (slot << 8))
|
||||
if mbVIA1
|
||||
mbVIA2 = mbTicklePSG(mbVIA1 + $80)
|
||||
return slot
|
||||
fin
|
||||
next
|
||||
fin
|
||||
fin
|
||||
return 0
|
||||
return -1
|
||||
end
|
||||
def psgSetup(pVIA)#0
|
||||
psgWrite(pVIA, MIXER, $3F) // Turn everything off
|
||||
@ -572,26 +644,25 @@ def mbSequence(yield, func)#0
|
||||
//
|
||||
seqTime++
|
||||
while !(mbVIA1->IFR & $40) // Wait for T1 interrupt
|
||||
if ^$C000 > 127; quit = TRUE; break; fin
|
||||
*rndseed++
|
||||
if a2keypressed(); quit = TRUE; break; fin
|
||||
loop
|
||||
mbVIA1->IFR = $40 // Clear interrupt
|
||||
if yieldTime <= seqTime; func()#0; yieldTime = seqTime + yield; fin
|
||||
until quit
|
||||
psgWrite(mbVIA1, MIXER, $FF) // Turn everything off
|
||||
psgWrite(mbVIA1, MIXER, $FF) // Turn everything off
|
||||
psgWrite(mbVIA1, AENVAMP, $00)
|
||||
psgWrite(mbVIA1, BENVAMP, $00)
|
||||
psgWrite(mbVIA1, CENVAMP, $00)
|
||||
if mbVIA2
|
||||
psgWrite(mbVIA2, MIXER, $FF)
|
||||
psgWrite(mbVIA2, MIXER, $FF)
|
||||
psgWrite(mbVIA2, AENVAMP, $00)
|
||||
psgWrite(mbVIA2, BENVAMP, $00)
|
||||
psgWrite(mbVIA2, CENVAMP, $00)
|
||||
fin
|
||||
mbVIA1->ACR = $00 // Stop T1 countdown
|
||||
//mbVIA1->ACR = $00 // Stop T1 countdown
|
||||
mbVIA1->IER = $7F // Mask all interrupts
|
||||
mbVIA1->IFR = $40 // Clear interrupt
|
||||
setStatusReg(status))
|
||||
setStatusReg(status)
|
||||
end
|
||||
//
|
||||
// Sequence notes through Apple II speaker
|
||||
@ -682,9 +753,8 @@ def spkrSequence(yield, func)#0
|
||||
if numNotes > 1
|
||||
for i = 0 to MAX_SPKR_NOTES-1
|
||||
if notes1[i]
|
||||
spkrTone(periods1[i], arpeggioDuration[numNotes])
|
||||
spkrTone(periods1[i], arpeggioDuration[numNotes])#0
|
||||
fin
|
||||
*rndseed++
|
||||
next
|
||||
seqTime++
|
||||
else
|
||||
@ -694,13 +764,12 @@ def spkrSequence(yield, func)#0
|
||||
period = periods1[i]
|
||||
break;
|
||||
fin
|
||||
*rndseed++
|
||||
next
|
||||
duration = eventTime - seqTime
|
||||
seqTime = duration + seqTime
|
||||
spkrTone(period, DUR16TH * duration)
|
||||
spkrTone(period, DUR16TH * duration)#0
|
||||
fin
|
||||
if ^$C000 > 127; return; fin
|
||||
if keypressed(); return; fin
|
||||
if yieldTime <= seqTime; func()#0; yieldTime = seqTime + yield; fin
|
||||
until FALSE
|
||||
end
|
||||
@ -718,9 +787,8 @@ def noSequence(yield, func)#0
|
||||
repeat
|
||||
seqTime++
|
||||
if seqTime < 0; seqTime = 1; fin // Capture wrap-around
|
||||
*rndseed++
|
||||
spkrTone(0, DUR16TH) // Waste 16th of a second playing silence
|
||||
if ^$C000 > 127; return; fin
|
||||
if keypressed(); return; fin
|
||||
if yield == seqTime; func()#0; seqTime = 0; fin
|
||||
until FALSE
|
||||
end
|
||||
@ -731,22 +799,17 @@ export def musicPlay(track, rept)#0
|
||||
byte i
|
||||
|
||||
//
|
||||
// First time search for MockingBoard
|
||||
// Select proper sequencer based on hardware
|
||||
//
|
||||
if mbVIA1 == -1
|
||||
if !mbSearch(0)
|
||||
//
|
||||
// No MockingBoard - scale octave0 for speaker
|
||||
//
|
||||
for i = 0 to 11
|
||||
spkrOctave0[i] = mbOctave0[i]/NOTEDIV
|
||||
next
|
||||
fin
|
||||
if mbSlot > 0
|
||||
musicSequence = @mbSequence
|
||||
else
|
||||
musicSequence = @spkrSequence
|
||||
fin
|
||||
//
|
||||
// Zero out active notes
|
||||
//
|
||||
for i = 0 to MAX_MBCH_NOTES-1; notes1[i] = 0; notes2[i] = 0; next
|
||||
for i = 0 to MAX_MBCH_NOTES-1; notes1[i] = 0; notes2[i] = 0; next
|
||||
for i = 0 to MAX_MBCH_NOTES-1; periods1[i] = 0; periods2[i] = 0; next
|
||||
//
|
||||
// Start sequencing
|
||||
@ -757,14 +820,6 @@ export def musicPlay(track, rept)#0
|
||||
seqTime = 0
|
||||
eventTime = seqEvent->deltatime
|
||||
numNotes = 0
|
||||
//
|
||||
// Select proper sequencer based on hardware
|
||||
//
|
||||
if mbVIA1
|
||||
musicSequence = @mbSequence
|
||||
else
|
||||
musicSequence = @spkrSequence
|
||||
fin
|
||||
end
|
||||
//
|
||||
// Stop sequencing music track
|
||||
@ -773,69 +828,48 @@ export def musicStop#0
|
||||
musicSequence = @noSequence
|
||||
end
|
||||
//
|
||||
// Get a keystroke and convert it to upper case
|
||||
// Play until keystroke
|
||||
//
|
||||
export def getUpperKey#1
|
||||
byte key
|
||||
|
||||
while ^$C000 < 128
|
||||
musicSequence($08, @backgroundProc)#0 // Call background proc every half second
|
||||
export def musicGetKey(yield, backgroundProc)#1
|
||||
while not keypressed()
|
||||
musicSequence(yield, backgroundProc)#0 // Call background proc every half second
|
||||
loop
|
||||
key = ^$C000 & $7F
|
||||
^$C010
|
||||
if key >= 'a' and key <= 'z'
|
||||
key = key - $20
|
||||
fin
|
||||
return key
|
||||
return getc
|
||||
end
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// More utility routines to test the getUpperKey routine
|
||||
//
|
||||
def putln#0
|
||||
putc($0D)
|
||||
end
|
||||
def puts(str)#0
|
||||
byte i
|
||||
|
||||
for i = 1 to ^str
|
||||
putc(^(str+i))
|
||||
when MACHID & MACHID_MODEL
|
||||
is MACHID_III
|
||||
spkrTone = @a3spkrTone
|
||||
spkrPWM = @a3spkrPWM
|
||||
keypressed = @a3keypressed
|
||||
break
|
||||
is MACHID_I
|
||||
puts("Sound unsupported.\n")
|
||||
return -1
|
||||
break
|
||||
otherwise
|
||||
//puts("MockingBoard Slot:\n")
|
||||
//puts("ENTER = None\n")
|
||||
//puts("0 = Scan\n")
|
||||
//puts("1-7 = Slot #\n")
|
||||
//instr = gets('>'|$80)
|
||||
//if ^instr
|
||||
// mbSlot = mbSearch(^(instr + 1) - '0')
|
||||
//fin
|
||||
mbSlot = mbSearch(0)
|
||||
break
|
||||
wend
|
||||
if mbSlot < 0
|
||||
//
|
||||
// No MockingBoard - scale octave0 for speaker
|
||||
//
|
||||
for instr = 0 to 11
|
||||
spkrOctave0[instr] = mbOctave0[instr]/NOTEDIV
|
||||
next
|
||||
end
|
||||
//
|
||||
// Sample background process
|
||||
//
|
||||
def backgroundProc#0
|
||||
^$0400++
|
||||
end
|
||||
//
|
||||
// Test functionality
|
||||
//
|
||||
def test#0
|
||||
byte key
|
||||
fin
|
||||
|
||||
puts("Press <RETURN> to exit:")
|
||||
while TRUE
|
||||
key = getUpperKey
|
||||
when key
|
||||
is $0D
|
||||
return
|
||||
is 'P'
|
||||
musicPlay(@toneTrack, TRUE)
|
||||
break
|
||||
is 'S'
|
||||
musicStop
|
||||
break
|
||||
otherwise
|
||||
putc(key)
|
||||
wend
|
||||
loop
|
||||
end
|
||||
|
||||
musicPlay(@toneTrack, TRUE)
|
||||
test
|
||||
musicStop
|
||||
done
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
There are three main externally callable routines in this module:
|
||||
@ -857,8 +891,8 @@ musicStop()
|
||||
The getUpperKey routine will call a dummy sequence routine that will
|
||||
keep the correct timing for any background processing
|
||||
|
||||
getUpperKey()
|
||||
Wait for a keypress and return the upper case character
|
||||
musicGetKey(yieldtime, yieldproc)
|
||||
Wait for a keypress and return the character
|
||||
While waiting for the keypress, the track sequence will be played though
|
||||
either the MockingBoard (if present) or the internal speaker. Optionally,
|
||||
a background function can be called periodically based on the sequencer
|
||||
@ -867,13 +901,13 @@ getUpperKey()
|
||||
The low level internal speaker routines used to generate tones and waveforms
|
||||
can be called for warnings, sound effects, etc:
|
||||
|
||||
spkrTone(period, duration)
|
||||
spkrTone(period, duration)#0
|
||||
Play a tone
|
||||
Params:
|
||||
(1020000 / 64 / period) Hz
|
||||
(duration * 32 * 256 / 1020000) seconds
|
||||
|
||||
spkrPWM(samples, speed, len)
|
||||
spkrPWM(samples, speed, len)#0
|
||||
Play a Pulse Width Modulated waveform
|
||||
Params:
|
||||
Pointer to 8 bit pulse width samples
|
6
Platform/Apple/virtual/src/plasma/sndseq.plh
Normal file
6
Platform/Apple/virtual/src/plasma/sndseq.plh
Normal file
@ -0,0 +1,6 @@
|
||||
import sndseq
|
||||
word musicSequence // musicSequence(yield, backgroundProc)#0
|
||||
word spkrTone // spkrTone(pitch, duration)#0
|
||||
word spkrPWM // spkrPWM(sample, speed, len)#0
|
||||
predef musicPlay(track, rept)#0, musicStop#0, musicGetKey(yield, backgroundProc)#1
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user