Merge branch 'master' of github.com:badvision/lawless-legends

This commit is contained in:
Martin Haye 2018-03-20 07:59:06 -07:00
commit 9a9464da3c
8 changed files with 441 additions and 169 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View 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