continued work on sound: migrate to SDL for host-based audio debugging

This commit is contained in:
Jorj Bauer 2017-02-26 11:00:41 -05:00
parent 969aa402ad
commit 39fdffd9c4
49 changed files with 1953 additions and 336 deletions

View File

@ -1,20 +1,30 @@
LDFLAGS=-L/usr/local/lib -lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_features2d -lopencv_calib3d
LDFLAGS=-L/usr/local/lib
CXXFLAGS=-Wall -I .. -I . -I apple -I opencv -O3
OPENCVLIBS=-lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_features2d -lopencv_calib3d
SDLLIBS=-lSDL2
CXXFLAGS=-Wall -I .. -I . -I apple -I opencv -I sdl -I/usr/local/include/SDL2 -O3 -g
TSRC=cpu.cpp util/testharness.cpp
OPENCVOBJS=cpu.o opencv/dummy-speaker.o opencv/opencv-display.o opencv/opencv-keyboard.o opencv/opencv-paddles.o opencv/opencv-filemanager.o apple/appledisplay.o apple/applekeyboard.o apple/applemmu.o apple/applevm.o apple/diskii.o apple/nibutil.o RingBuffer.o globals.o opencv/aiie.o apple/parallelcard.o apple/fx80.o opencv/opencv-printer.o apple/mockingboard.o apple/sy6522.o apple/ay8910.o
COMMONOBJS=cpu.o apple/appledisplay.o apple/applekeyboard.o apple/applemmu.o apple/applevm.o apple/diskii.o apple/nibutil.o RingBuffer.o globals.o apple/parallelcard.o apple/fx80.o apple/mockingboard.o apple/sy6522.o apple/ay8910.o
OPENCVOBJS=opencv/dummy-speaker.o opencv/opencv-display.o opencv/opencv-keyboard.o opencv/opencv-paddles.o opencv/opencv-filemanager.o opencv/aiie.o opencv/opencv-printer.o
SDLOBJS=sdl/sdl-speaker.o sdl/sdl-display.o sdl/sdl-keyboard.o sdl/sdl-paddles.o sdl/sdl-filemanager.o sdl/aiie.o sdl/sdl-printer.o
ROMS=apple/applemmu-rom.h apple/diskii-rom.h apple/parallel-rom.h
all: opencv
all: sdl
opencv: roms $(OPENCVOBJS)
g++ $(LDFLAGS) -o aiie-opencv $(OPENCVOBJS)
opencv: roms $(COMMONOBJS) $(OPENCVOBJS)
g++ $(LDFLAGS) $(OPENCVLIBS) -o aiie-opencv $(COMMONOBJS) $(OPENCVOBJS)
sdl: roms $(COMMONOBJS) $(SDLOBJS)
g++ $(LDFLAGS) $(SDLLIBS) $(OPENCVLIBS) -o aiie-sdl $(COMMONOBJS) $(SDLOBJS)
clean:
rm -f *.o *~ */*.o */*~ testharness.basic testharness.verbose testharness.extended aiie-opencv apple/diskii-rom.h apple/applemmu-rom.h apple/parallel-rom.h
rm -f *.o *~ */*.o */*~ testharness.basic testharness.verbose testharness.extended aiie-opencv apple/diskii-rom.h apple/applemmu-rom.h apple/parallel-rom.h aiie-sdl
test: $(TSRC)
g++ $(CXXFLAGS) -DBASICTEST $(TSRC) -o testharness.basic

View File

@ -373,7 +373,7 @@ uint8_t AppleMMU::readSwitches(uint16_t address)
case 0xC030: // SPEAKER
g_speaker->toggleAtCycle(g_cpu->cycles);
g_speaker->toggle();
break;
case 0xC050: // CLRTEXT

View File

@ -60,7 +60,6 @@ void AppleVM::cpuMaintenance(uint32_t cycles)
}
keyboard->maintainKeyboard(cycles);
parallel->update();
mockingboard->update(cycles);
}

View File

@ -3,6 +3,46 @@
#include "globals.h"
// Map our linear 4-bit amplitude to 8-bit output level
static const uint8_t volumeLevels[16] = { 0x00, 0x04, 0x05, 0x07,
0x0B, 0x10, 0x16, 0x23,
0x2B, 0x44, 0x5A, 0x73,
0x92, 0xB0, 0xD9, 0xFF };
// Envelope constants
enum {
AY_ENV_HOLD = 1,
AY_ENV_ALT = 2,
AY_ENV_ATTACK = 4,
AY_ENV_CONT = 8
};
/* Envelope handling
* (Per General Instruments AY-3-8910 documentation.)
*
* Envelope period is set in the 16-bit value r[0x0C]:r[0x0B] (where 0 = 1).
* The resulting frequency is from 0.12Hz to 7812.5 Hz.
*
* The shape of the envelope is selected by r[0x0D] and uses the
* constants above.
*
* If AY_ENV_HOLD is set, then when the envelope reaches terminal (0
* or 15) it stays there.
*
* If AY_ENV_ALT is set, the direction reverses each time it reaches
* terminal. (If both AY_ENV_HOLD and AY_ENV_ALT are set, then the
* envelope counter returns to its initial count before holding.)
*
* If AY_ENV_ATTACK is set, the counter is ascending (0-to-15); otherwise
* it is descending (15-to-0).
*
* If AY_ENV_CONT is *clear* (0), then the counter resets to 0 after
* one cycle and holds there. If it is 1, it does whatever HOLD
* says. (So AY_ENV_CONT==0 takes priority over AY_ENV_HOLD).
*
*
*/
AY8910::AY8910()
{
Reset();
@ -10,12 +50,16 @@ AY8910::AY8910()
void AY8910::Reset()
{
printf("AY8910 reset\n");
curRegister = 0;
// FIXME: what are the right default values?
for (uint8_t i=0; i<16; i++)
r[i] = 0xFF;
r[i] = 0x00;
waveformFlipTimer[0] = waveformFlipTimer[1] = waveformFlipTimer[2] = 0;
outputState[0] = outputState[1] = outputState[2] = 0;
envCounter = 0;
envelopeTimer = envelopeTime = 0;
envDirection = 1;
}
uint8_t AY8910::read(uint8_t reg)
@ -31,7 +75,7 @@ void AY8910::write(uint8_t reg, uint8_t PortA)
{
// Bit 2 (1 << 2 == 0x04) is wired to the Reset pin. If it goes low,
// we reset the virtual chip.
if ((reg & 0x04) == 0) {
if ((reg & NRSET) == 0) {
Reset();
return;
}
@ -42,30 +86,52 @@ void AY8910::write(uint8_t reg, uint8_t PortA)
reg &= ~0x04;
switch (reg) {
case 0: // bDir==0 && BC1 == 0 (IAB)
case IAB: // bDir==0 && BC1 == 0 (IAB)
// Puts the DA bus in high-impedance state. Nothing for us to do?
return;
case 1: // bDir==0 && BC1 == 1 (DTB)
case DTB: // bDir==0 && BC1 == 1 (DTB)
// Contents of the currently addressed register are put in DA. FIXME?
return;
case 2: // bDir==1 && BC1 == 0 (DWS)
case DWS: // bDir==1 && BC1 == 0 (DWS)
// Write current PortA to PSG
printf("Set register %d to %X\n", reg, PortA);
r[curRegister] = PortA;
if (curRegister <= 1) {
cycleTime[0] = cycleTimeForPSG(0);
} else if (curRegister <= 3) {
cycleTime[1] = cycleTimeForPSG(1);
} else if (curRegister <= 5) {
cycleTime[2] = cycleTimeForPSG(2);
} else if (curRegister == 7) {
if (curRegister <= CHAN_A_COARSE) {
cycleTime[0] = cycleTimeForPSG(0);
waveformFlipTimer[0] = g_cpu->cycles + cycleTime[0];
} else if (curRegister <= CHAN_B_COARSE) {
cycleTime[1] = cycleTimeForPSG(1);
waveformFlipTimer[1] = g_cpu->cycles + cycleTime[1];
} else if (curRegister <= CHAN_C_COARSE) {
cycleTime[2] = cycleTimeForPSG(2);
waveformFlipTimer[2] = g_cpu->cycles + cycleTime[2];
} else if (curRegister == ENAB) {
if (r[ENAB] & ENAB_N_TONEA) {
cycleTime[0] = waveformFlipTimer[0] = 0;
} else {
cycleTime[0] = cycleTimeForPSG(0);
waveformFlipTimer[0] = g_cpu->cycles + cycleTime[0];
}
if (r[ENAB] & ENAB_N_TONEB) {
cycleTime[1] = waveformFlipTimer[1] = 0;
} else {
cycleTime[1] = cycleTimeForPSG(1);
waveformFlipTimer[1] = g_cpu->cycles + cycleTime[1];
}
if (r[ENAB] & ENAB_N_TONEC) {
cycleTime[2] = waveformFlipTimer[2] = 0;
} else {
cycleTime[2] = cycleTimeForPSG(2);
waveformFlipTimer[2] = g_cpu->cycles + cycleTime[2];
}
} else if (curRegister >= ENV_PERIOD_FINE && curRegister <= ENV_SHAPE) {
// Envelope control -- period or shape
// FIXME: should envCounter be initialized to the start position?
envDirection = (r[ENV_SHAPE] & AY_ENV_ATTACK) ? 1 : -1;
envelopeTime = calculateEnvelopeTime();
envelopeTimer = 0; // reset so it will pick up @ next tick
}
return;
case 3: // bDir==1 && BC1 == 1 (INTAK)
case INTAK: // bDir==1 && BC1 == 1 (INTAK)
// Select current register
curRegister = PortA & 0xF;
return;
@ -97,33 +163,108 @@ uint16_t AY8910::cycleTimeForPSG(uint8_t psg)
return (32 * regVal) / 4;
}
// Similar calculation: this one, for the envelope timer.
// FIXME: I *think* this is right. Not sure. Needs validation.
uint32_t AY8910::calculateEnvelopeTime()
{
uint16_t regVal = (r[0x0C] << 8) | (r[0x0B]);
if (regVal == 0) regVal++;
return (512 * regVal) / 4;
}
void AY8910::update(uint32_t cpuCycleCount)
{
#if 0
// Debugging: print state of the 16 registers
printf("AY8910: ");
for (int i=0; i<16; i++) {
printf("%02X ", r[i]);
}
printf("%04X %04X %04X\n", cycleTime[0], cycleTime[1], cycleTime[2]);
#endif
// update the envelope timer if it's time
if (envelopeTime != 0) {
if (!envelopeTimer) {
// timer wasn't set, so start it running
envelopeTimer = cpuCycleCount + envelopeTime;
}
if (envelopeTimer <= cpuCycleCount) {
// time to update the envelopeCounter.
switch (r[ENV_SHAPE]) {
// Continue / Attack / Alternate / Hold bits
case 0x00: // 0 / 0 / x / x -- descend once, stay @ bottom
case 0x01: // 0 / 0 / x / x
case 0x02: // 0 / 0 / x / x
case 0x03: // 0 / 0 / x / x
case 0x04: // 0 / 1 / x / x -- ascend once, jump to bottom
case 0x05: // 0 / 1 / x / x
case 0x06: // 0 / 1 / x / x
case 0x07: // 0 / 1 / x / x
case 0x09: // 1 / 0 / 0 / 1 -- descend once, stay @ bottom
case 0x0b: // 1 / 0 / 1 / 1 -- descend once, jump to top
case 0x0d: // 1 / 1 / 0 / 1 -- ascend once, stay @ top
case 0x0f: // 1 / 1 / 1 / 1 -- ascend once, jump to bottom
// In all these cases, we go from start to finish once. In all
// cases except 0x0b and 0x0d, when we're done, we go low.
envCounter += envDirection;
if (envDirection > 0) {
// We were ascending: did we hit 16? If so, stop & go terminal
if (envCounter == 16) {
envDirection = 0;
// One ascending case (0x0b) goes high after; all others are low
envCounter = (r[ENV_SHAPE] == 0x0b ? 0x0F : 0x00);
}
} else if (envDirection < 0) {
// We were descending: did we hit 0? If so, stop & go terminal
if (envCounter == 0) {
envDirection = 0;
// One descending case (0x0d) goes high after; all others are low
envCounter = (r[ENV_SHAPE] == 0x0d ? 0x0F : 0x00);
}
}
}
// Set up the envelope timer for the next transition
// FIXME: can set this to 0 if envDirection is 0, but have to be careful about setup of timer again when envDirection is re-set
envelopeTimer += envelopeTime;
}
}
// For any waveformFlipTimer that is > 0: if cpuCycleCount is larger
// than the timer, we'll flip state. (It's a square wave!)
for (uint8_t i=0; i<3; i++) {
uint32_t cc = cycleTime[i];
if (cc == 0) {
waveformFlipTimer[i] = 0;
} else {
if (!waveformFlipTimer[i]) {
// start a cycle, if necessary
waveformFlipTimer[i] = cpuCycleCount + cc;
}
if (waveformFlipTimer[i] && waveformFlipTimer[i] <= cpuCycleCount) {
if (cc) {
if (waveformFlipTimer[i] <= cpuCycleCount) {
// flip when it's time to flip
waveformFlipTimer[i] += cc;
outputState[i] = !outputState[i];
}
} else {
outputState[i] = 0;
}
// If any of the square waves is on, then we want to be on.
// r[i+8] is the amplitude control.
// FIXME: if r[i+8] & 0x10, then it's an envelope-specific amplitude
g_speaker->mixOutput(outputState[i] ? (r[i+8] & 0x0F) : 0x00);
// Figure out what output comes from this channel and send it to
// the speaker. The output is controlled by outputState[i] (from
// the square wave, above); the amplitude control line for this
// output (r[i+8], below) and the tone/noise selection (FIXME:
// currently unimplemented).
uint8_t amplitude = r[i+8] & 0xF;
// ... and if bit 0x10 is on, it's controlled by the envelope counter.
if (r[i+8] & 0x10)
amplitude = envCounter;
g_speaker->mixOutput(outputState[i] ? volumeLevels[amplitude] : 0x00);
}
}

View File

@ -3,6 +3,43 @@
#include <stdint.h>
// Operations...
enum {
IAB = 0,
DTB = 1,
DWS = 2,
INTAK = 3,
NRSET = 4
};
// Registers...
enum {
CHAN_A_FINE = 0,
CHAN_A_COARSE = 1,
CHAN_B_FINE = 2,
CHAN_B_COARSE = 3,
CHAN_C_FINE = 4,
CHAN_C_COARSE = 5,
NOISE_PERIOD = 6,
ENAB = 7,
CHAN_A_AMP = 8,
CHAN_B_AMP = 9,
CHAN_C_AMP = 10,
ENV_PERIOD_FINE = 11,
ENV_PERIOD_COARSE = 12,
ENV_SHAPE = 13
};
// Enable flags (all negative; enabled-low)
enum {
ENAB_N_TONEA = 1,
ENAB_N_TONEB = 2,
ENAB_N_TONEC = 4,
ENAB_N_NOISEA = 8,
ENAB_N_NOISEB = 16,
ENAB_N_NOISEC = 32
};
class AY8910 {
public:
AY8910();
@ -16,13 +53,18 @@ class AY8910 {
protected:
uint16_t cycleTimeForPSG(uint8_t psg);
uint32_t calculateEnvelopeTime();
private:
uint8_t curRegister;
uint8_t r[16];
uint32_t waveformFlipTimer[3];
uint16_t cycleTime[3]; // how long each cycle will last, in clock cycles
uint32_t waveformFlipTimer[3]; // when we're going to flip next
uint8_t outputState[3];
uint16_t cycleTime[3];
int8_t envCounter; // which bit of the waveform the envelope is on
int8_t envDirection;
uint32_t envelopeTime;
uint32_t envelopeTimer;
};
#endif

View File

@ -429,12 +429,3 @@ void Fx80::emitLine()
g_printer->addLine(rowOfBits);
}
void Fx80::update()
{
// The onscreen window needs to update regularly, and we probably
// want to periodically flush the Teensy buffer.
if (g_printer) {
g_printer->update();
}
}

View File

@ -60,8 +60,6 @@ class Fx80 {
void input(uint8_t c);
void update();
private:
void lineFeed();
void clearLine();

View File

@ -38,9 +38,7 @@ uint8_t Mockingboard::read(uint16_t address)
(address >= 0x80 &&
address <= 0x8F) ) {
uint8_t idx = (address & 0x80 ? 1 : 0);
if (idx == 0) { // FIXME: just debugging; remove this 'if'
return sy6522[idx].read(address & 0x0F);
}
return sy6522[idx].read(address & 0x0F);
}
return 0xFF;
@ -54,16 +52,13 @@ void Mockingboard::write(uint16_t address, uint8_t val)
(address >= 0x80 &&
address <= 0x8F) ) {
uint8_t idx = (address & 0x80 ? 1 : 0);
if (idx == 0) { // FIXME: just debugging; remove this 'if'
return sy6522[idx].write(address & 0x0F, val);
}
return sy6522[idx].write(address & 0x0F, val);
}
}
void Mockingboard::update(uint32_t cycles)
{
sy6522[0].update(cycles);
// debugging: disabled the second update for the moment
// sy6522[1].update(cycles);
sy6522[1].update(cycles);
}

View File

@ -43,8 +43,3 @@ void ParallelCard::loadROM(uint8_t *toWhere)
memcpy(toWhere, romData, 256);
#endif
}
void ParallelCard::update()
{
fx80->update();
}

View File

@ -22,8 +22,6 @@ class ParallelCard : public Slot {
virtual uint8_t readSwitches(uint8_t s);
virtual void writeSwitches(uint8_t s, uint8_t v);
virtual void loadROM(uint8_t *toWhere);
void update();
private:
Fx80 *fx80;

View File

@ -57,7 +57,6 @@ uint8_t SY6522::read(uint8_t address)
void SY6522::write(uint8_t address, uint8_t val)
{
printf("SY6522: %X = %02X\n", address, val);
switch (address) {
case SY_ORB:
val &= DDRB;

View File

@ -210,7 +210,7 @@ int main(int argc, char *argv[])
g_vm->Reset();
g_cpu->rst();
g_display->blit();
// g_display->blit();
g_display->redraw();
if (argc >= 2) {
@ -242,10 +242,11 @@ int main(int argc, char *argv[])
// Make this a little friendlier, and the expense of some framerate?
// usleep(10000);
if (g_vm->vmdisplay->needsRedraw()) {
AiieRect what = g_vm->vmdisplay->getDirtyRect();
// make sure to clear the flag before drawing; there's no lock
// on didRedraw, so the other thread might update it
g_vm->vmdisplay->didRedraw();
g_display->blit();
g_display->blit(what);
}
g_keyboard->maintainKeyboard();

View File

@ -1,99 +1,26 @@
#include "dummy-speaker.h"
#include <pthread.h>
#include "timeutil.h"
// FIXME: Globals; ick.
static pthread_t speakerThreadID;
static uint8_t curSpeakerData = 0x00;
static uint64_t hitcount;
static uint64_t misscount;
static void *speaker_thread(void *dummyptr) {
struct timespec currentTime;
struct timespec startTime;
struct timespec nextSampleTime;
_init_darwin_shim();
do_gettime(&startTime);
do_gettime(&nextSampleTime);
FILE *f = popen("play -q -t raw -b 8 -e unsigned-integer -r 8000 -", "w");
uint64_t sampleCount = 0;
while (1) {
do_gettime(&currentTime);
struct timespec diff = tsSubtract(nextSampleTime, currentTime);
if (diff.tv_sec >= 0 && diff.tv_nsec >= 0) {
nanosleep(&diff, NULL);
hitcount++;
} else
misscount++;
if ((sampleCount & 0xFFFF) == 0) {
printf("sound hit: %lld miss: %lld\n", hitcount, misscount);
}
fputc(curSpeakerData & 0xFF, f); fflush(f);
nextSampleTime = startTime;
timespec_add_ms(&startTime, sampleCount * 1000 / 8000, &nextSampleTime);
sampleCount++;
}
}
DummySpeaker::DummySpeaker()
{
mixerValue = 0;
_init_darwin_shim(); // set up the clock interface
if (!pthread_create(&speakerThreadID, NULL, &speaker_thread, (void *)NULL)) {
printf("speaker thread created\n");
}
}
DummySpeaker::~DummySpeaker()
{
pclose(f);
}
void DummySpeaker::toggleAtCycle(uint32_t c)
void DummySpeaker::toggle()
{
nextTransitionAt = c;
}
void DummySpeaker::maintainSpeaker(uint32_t c)
{
/* if (nextTransitionAt && c >= nextTransitionAt) {
// Override the mixer with a 1-bit "Terribad" audio sample change
mixerValue = speakerState ? 0x00 : (0xFF<<3); // <<3 b/c of the >>=3 below
nextTransitionAt = 0;
}*/
if (numMixed) {
mixerValue /= numMixed;
}
speakerState = mixerValue;
// FIXME: duplication of above? using a global? fix fix fix.
curSpeakerData = mixerValue & 0xFF;
}
bool DummySpeaker::currentState()
{
return speakerState;
}
void DummySpeaker::beginMixing()
{
mixerValue = 0;
numMixed = 0;
}
void DummySpeaker::mixOutput(uint8_t v)
{
mixerValue += v;
numMixed++;
}

View File

@ -10,19 +10,10 @@ class DummySpeaker : public PhysicalSpeaker {
DummySpeaker();
virtual ~DummySpeaker();
virtual void toggleAtCycle(uint32_t c);
virtual void toggle();
virtual void maintainSpeaker(uint32_t c);
virtual bool currentState();
virtual void beginMixing();
virtual void mixOutput(uint8_t v);
private:
bool speakerState;
uint32_t mixerValue;
uint8_t numMixed;
uint32_t nextTransitionAt;
FILE *f;
};
#endif

View File

@ -10,7 +10,7 @@
using namespace cv;
using namespace std;
#define WINDOWNAME "6502core"
#define WINDOWNAME "Aiie!"
#include "bios-font.h"
#include "display-bg.h"
@ -146,12 +146,11 @@ void OpenCVDisplay::drawBatteryStatus(uint8_t percent)
#define BASEX 36
#define BASEY 26
void OpenCVDisplay::blit()
void OpenCVDisplay::blit(AiieRect r)
{
uint8_t *videoBuffer = g_vm->videoBuffer; // FIXME: poking deep
for (uint8_t y=0; y<192; y++) {
for (uint16_t x=0; x<280; x++) {
for (uint8_t y=r.top; y<=r.bottom; y++) {
for (uint16_t x=r.left; x<=r.right; x++) {
uint16_t pixel = (y*320+x)/2;
uint8_t colorIdx;
if (x & 1) {

View File

@ -23,7 +23,7 @@ class OpenCVDisplay : public PhysicalDisplay {
OpenCVDisplay();
virtual ~OpenCVDisplay();
virtual void blit();
virtual void blit(AiieRect r);
virtual void redraw();
virtual void drawDriveDoor(uint8_t which, bool isOpen);

View File

@ -47,14 +47,14 @@ static void timespec_add_cycles(struct timespec *start,
// adds the number of microseconds given to *start and
// returns it in *out
static void timespec_add_ms(struct timespec *start,
static void timespec_add_us(struct timespec *start,
uint64_t micros,
struct timespec *out)
{
out->tv_sec = start->tv_sec;
out->tv_nsec = start->tv_nsec;
uint64_t nanosToAdd = micros * 1000000L;
uint64_t nanosToAdd = micros * 1000L;
out->tv_sec += (nanosToAdd / NANOSECONDS_PER_SECOND);
out->tv_nsec += (nanosToAdd % NANOSECONDS_PER_SECOND);

View File

@ -3,13 +3,15 @@
#include <string.h> // strncpy
#include "vmdisplay.h" // FIXME: for AiieRect
class PhysicalDisplay {
public:
PhysicalDisplay() { overlayMessage[0] = '\0'; }
virtual ~PhysicalDisplay() {};
virtual void redraw() = 0; // total redraw, assuming nothing
virtual void blit() = 0; // redraw just the VM display area
virtual void blit(AiieRect r) = 0; // redraw just the VM display area
virtual void drawDriveDoor(uint8_t which, bool isOpen) = 0;
virtual void drawDriveStatus(uint8_t which, bool isRunning) = 0;

View File

@ -7,11 +7,10 @@ class PhysicalSpeaker {
public:
virtual ~PhysicalSpeaker() {}
virtual void toggleAtCycle(uint32_t c) = 0;
virtual void toggle() = 0;
virtual void maintainSpeaker(uint32_t c) = 0;
virtual void beginMixing() = 0;
virtual void mixOutput(uint8_t v) = 0;
virtual bool currentState() = 0;
};

297
sdl/aiie.cpp Normal file
View File

@ -0,0 +1,297 @@
#include <stdio.h>
#include <unistd.h>
#include <curses.h>
#include <termios.h>
#include <pthread.h>
#include "applevm.h"
#include "sdl-display.h"
#include "sdl-keyboard.h"
#include "sdl-speaker.h"
#include "sdl-paddles.h"
#include "sdl-filemanager.h"
#include "sdl-printer.h"
#include "globals.h"
#include "timeutil.h"
//#define SHOWFPS
//#define SHOWPC
//#define DEBUGCPU
//#define SHOWMEMPAGE
static struct timespec nextInstructionTime, startTime;
uint64_t hitcount = 0;
uint64_t misscount = 0;
#define NB_ENABLE 1
#define NB_DISABLE 0
int send_rst = 0;
pthread_t cpuThreadID;
void sigint_handler(int n)
{
send_rst = 1;
}
void nonblock(int state)
{
struct termios ttystate;
//get the terminal state
tcgetattr(STDIN_FILENO, &ttystate);
if (state==NB_ENABLE)
{
//turn off canonical mode
ttystate.c_lflag &= ~ICANON;
//minimum of number input read.
ttystate.c_cc[VMIN] = 1;
}
else if (state==NB_DISABLE)
{
//turn on canonical mode
ttystate.c_lflag |= ICANON;
}
//set the terminal attributes.
tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);
}
uint8_t read(void *arg, uint16_t address)
{
// no action; this is a dummy function until we've finished initializing...
return 0x00;
}
void write(void *arg, uint16_t address, uint8_t v)
{
// no action; this is a dummy function until we've finished initializing...
}
static void *cpu_thread(void *dummyptr) {
struct timespec currentTime;
#if 0
int policy;
struct sched_param param;
pthread_getschedparam(pthread_self(), &policy, &param);
param.sched_priority = sched_get_priority_max(policy);
pthread_setschedparam(pthread_self(), policy, &param);
#endif
_init_darwin_shim();
do_gettime(&startTime);
do_gettime(&nextInstructionTime);
printf("free-running\n");
while (1) {
// cycle down the CPU...
do_gettime(&currentTime);
struct timespec diff = tsSubtract(nextInstructionTime, currentTime);
if (diff.tv_sec >= 0 && diff.tv_nsec >= 0) {
hitcount++;
nanosleep(&diff, NULL);
} else {
misscount++;
}
#ifdef DEBUGCPU
uint8_t executed = g_cpu->Run(1);
#else
uint8_t executed = g_cpu->Run(24);
#endif
timespec_add_cycles(&startTime, g_cpu->cycles + executed, &nextInstructionTime);
g_speaker->beginMixing();
// The paddles need to be triggered in real-time on the CPU
// clock. That happens from the VM's CPU maintenance poller.
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles);
// cpuMaintenance also maintained the sound card; update the speaker after
g_speaker->maintainSpeaker(g_cpu->cycles);
#ifdef DEBUGCPU
{
uint8_t p = g_cpu->flags;
printf("OP: $%02x A: %02x X: %02x Y: %02x PC: $%04x SP: %02x Flags: %c%cx%c%c%c%c%c\n",
g_vm->getMMU()->read(g_cpu->pc),
g_cpu->a, g_cpu->x, g_cpu->y, g_cpu->pc, g_cpu->sp,
p & (1<<7) ? 'N':' ',
p & (1<<6) ? 'V':' ',
p & (1<<4) ? 'B':' ',
p & (1<<3) ? 'D':' ',
p & (1<<2) ? 'I':' ',
p & (1<<1) ? 'Z':' ',
p & (1<<0) ? 'C':' '
);
}
#endif
if (send_rst) {
#if 1
printf("Sending reset\n");
g_cpu->Reset();
// testing startup keyboard presses - perform Apple //e self-test
//g_vm->getKeyboard()->keyDepressed(RA);
//g_vm->Reset();
//g_cpu->Reset();
//((AppleVM *)g_vm)->insertDisk(0, "disks/DIAGS.DSK");
#else
MMU *mmu = g_vm->getMMU();
printf("PC: 0x%X\n", g_cpu->pc);
for (int i=g_cpu->pc; i<g_cpu->pc + 0x100; i++) {
printf("0x%X ", mmu->read(i));
}
printf("\n");
printf("Dropping to monitor\n");
// drop directly to monitor.
g_cpu->pc = 0xff69; // "call -151"
mmu->read(0xC054); // make sure we're in page 1
mmu->read(0xC056); // and that hires is off
mmu->read(0xC051); // and text mode is on
mmu->read(0xC08A); // and we have proper rom in place
mmu->read(0xc008); // main zero-page
mmu->read(0xc006); // rom from cards
mmu->write(0xc002 + mmu->read(0xc014)? 1 : 0, 0xff); // make sure aux ram read and write match
mmu->write(0x20, 0); // text window
mmu->write(0x21, 40);
mmu->write(0x22, 0);
mmu->write(0x23, 24);
mmu->write(0x33, '>');
mmu->write(0x48, 0); // from 0xfb2f: part of text init
#endif
send_rst = 0;
}
}
}
int main(int argc, char *argv[])
{
SDL_Init(SDL_INIT_EVERYTHING);
g_speaker = new SDLSpeaker();
g_printer = new SDLPrinter();
// create the filemanager - the interface to the host file system.
g_filemanager = new SDLFileManager();
g_display = new SDLDisplay();
// paddles have to be created after g_display created the window
g_paddles = new SDLPaddles();
// Next create the virtual CPU. This needs the VM's MMU in order to run, but we don't have that yet.
g_cpu = new Cpu();
// Create the virtual machine. This may read from g_filemanager to get ROMs if necessary.
// (The actual Apple VM we've built has them compiled in, though.) It will create its virutal
// hardware (MMU, video driver, floppy, paddles, whatever).
g_vm = new AppleVM();
g_keyboard = new SDLKeyboard(g_vm->getKeyboard());
// Now that the VM exists and it has created an MMU, we tell the CPU how to access memory through the MMU.
g_cpu->SetMMU(g_vm->getMMU());
// Now that all the virtual hardware is glued together, reset the VM
g_vm->Reset();
g_cpu->rst();
// g_display->blit();
g_display->redraw();
if (argc >= 2) {
printf("Inserting disk %s\n", argv[1]);
((AppleVM *)g_vm)->insertDisk(0, argv[1]);
}
if (argc == 3) {
printf("Inserting disk %s\n", argv[2]);
((AppleVM *)g_vm)->insertDisk(1, argv[2]);
}
nonblock(NB_ENABLE);
signal(SIGINT, sigint_handler);
printf("creating CPU thread\n");
if (!pthread_create(&cpuThreadID, NULL, &cpu_thread, (void *)NULL)) {
printf("thread created\n");
// pthread_setschedparam(cpuThreadID, SCHED_RR, PTHREAD_MAX_PRIORITY);
}
while (1) {
static uint32_t ctr = 0;
if (++ctr == 0) {
printf("hit: %llu; miss: %llu; pct: %f\n", hitcount, misscount, (double)misscount / (double)(misscount + hitcount));
}
// Make this a little friendlier, and the expense of some framerate?
// usleep(10000);
if (g_vm->vmdisplay->needsRedraw()) {
AiieRect what = g_vm->vmdisplay->getDirtyRect();
// make sure to clear the flag before drawing; there's no lock
// on didRedraw, so the other thread might update it
g_vm->vmdisplay->didRedraw();
g_display->blit(what);
}
g_printer->update();
g_keyboard->maintainKeyboard();
g_display->drawBatteryStatus(100);
#ifdef SHOWFPS
static time_t startAt = time(NULL);
static uint32_t loopCount = 0;
loopCount++;
time_t lenSecs = time(NULL) - startAt;
if (lenSecs >= 10) {
char buf[25];
sprintf(buf, "%lu FPS", loopCount / lenSecs);
g_display->debugMsg(buf);
if (lenSecs >= 60) {
startAt = time(NULL);
loopCount = 0;
}
}
#endif
#ifdef SHOWPC
{
char buf[25];
sprintf(buf, "%X", g_cpu->pc);
g_display->debugMsg(buf);
}
#endif
#ifdef SHOWMEMPAGE
{
char buf[40];
sprintf(buf, "AUX %c/%c BNK %d BSR %c/%c ZP %c 80 %c INT %c",
g_vm->auxRamRead?'R':'_',
g_vm->auxRamWrite?'W':'_',
g_vm->bank1,
g_vm->readbsr ? 'R':'_',
g_vm->writebsr ? 'W':'_',
g_vm->altzp ? 'Y':'_',
g_vm->_80store ? 'Y' : '_',
g_vm->intcxrom ? 'Y' : '_');
g_display->debugMsg(buf);
}
#endif
}
}

274
sdl/sdl-display.cpp Normal file
View File

@ -0,0 +1,274 @@
#include <ctype.h> // isgraph
#include "sdl-display.h"
#include "bios-font.h"
#include "display-bg.h"
#include "globals.h"
#include "applevm.h"
// RGB map of each of the lowres colors
const uint8_t loresPixelColors[16][3] = { { 0, 0, 0 }, // black
{ 195, 0, 48 }, // magenta
{ 0, 0, 130 }, // dark blue
{ 166, 52, 170 }, // purple
{ 0, 146, 0 }, // dark green
{ 105, 105, 105 }, // drak grey
{ 24, 113, 255 }, // medium blue
{ 12, 190, 235 }, // light blue
{ 150, 85, 40 }, // brown
{ 255, 24, 44 }, // orange
{ 150, 170, 170 }, // light gray
{ 255, 158, 150 }, // pink
{ 0, 255, 0 }, // green
{ 255, 255, 0 }, // yellow
{ 130, 255, 130 }, // aqua
{ 255, 255, 255 } // white
};
SDLDisplay::SDLDisplay()
{
// FIXME: abstract constants
screen = SDL_CreateWindow("Aiie!",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
320*2, 240*2,
SDL_WINDOW_SHOWN);
// SDL_RENDERER_SOFTWARE because, at least on my Mac, this has some
// serious issues with hardware accelerated drawing (flashing and crashing).
renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_SOFTWARE);
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); // set to white
SDL_RenderClear(renderer); // clear it to the selected color
SDL_RenderPresent(renderer); // perform the render
}
SDLDisplay::~SDLDisplay()
{
SDL_Quit();
}
void SDLDisplay::redraw()
{
// primarily for the device, where it's in and out of the
// bios. Draws the background image.
printf("redraw background\n");
for (int y=0; y<240; y++) {
for (int x=0; x<320; x++) {
uint8_t *p = &displayBitmap[(y * 320 + x)*3];
drawPixel(x, y, p[0], p[1], p[2]);
}
}
if (g_vm) {
drawDriveDoor(0, ((AppleVM *)g_vm)->DiskName(0)[0] == '\0');
drawDriveDoor(1, ((AppleVM *)g_vm)->DiskName(1)[0] == '\0');
}
}
void SDLDisplay::drawDriveStatus(uint8_t which, bool isRunning)
{
// FIXME: this is a draw from another thread. Can't do that with SDL.
return;
// location of status indicator for left drive
uint16_t xoff = 125;
uint16_t yoff = 213;
// and right drive
if (which == 1)
xoff += 135;
for (int y=0; y<1; y++) {
for (int x=0; x<6; x++) {
drawPixel(x + xoff, y + yoff, isRunning ? 0xF800 : 0x8AA9);
}
}
}
void SDLDisplay::drawDriveDoor(uint8_t which, bool isOpen)
{
// location of drive door for left drive
uint16_t xoff = 55;
uint16_t yoff = 216;
// location for right drive
if (which == 1) {
xoff += 134;
}
for (int y=0; y<20; y++) {
for (int x=0; x<43; x++) {
uint8_t *p = &driveLatch[(y * 43 + x)*3];
if (isOpen) {
p = &driveLatchOpen[(y * 43 + x)*3];
}
drawPixel(x+xoff, y+yoff, p[0], p[1], p[2]);
}
}
}
void SDLDisplay::drawBatteryStatus(uint8_t percent)
{
uint16_t xoff = 300;
uint16_t yoff = 222;
// the area around the apple is 12 wide
// it's exactly 11 high
// the color is 210/202/159
float watermark = ((float)percent / 100.0) * 11;
for (int y=0; y<11; y++) {
uint8_t bgr = 210;
uint8_t bgg = 202;
uint8_t bgb = 159;
if (11-y > watermark) {
// black...
bgr = bgg = bgb = 0;
}
for (int x=0; x<11; x++) {
uint8_t *p = &appleBitmap[(y * 10 + (x-1))*4];
// It's RGBA; blend w/ background color
uint8_t r,g,b;
float alpha = (float)p[3] / 255.0;
r = (float)p[0] * alpha + (bgr * (1.0 - alpha));
g = (float)p[1] * alpha + (bgg * (1.0 - alpha));
b = (float)p[2] * alpha + (bgb * (1.0 - alpha));
drawPixel(x+xoff, y+yoff, r, g, b);
}
}
}
#define BASEX 36
#define BASEY 26
void SDLDisplay::blit(AiieRect r)
{
uint8_t *videoBuffer = g_vm->videoBuffer; // FIXME: poking deep
for (uint8_t y=0; y<192; y++) {
for (uint16_t x=0; x<280; x++) {
uint16_t pixel = (y*320+x)/2;
uint8_t colorIdx;
if (x & 1) {
colorIdx = videoBuffer[pixel] & 0x0F;
} else {
colorIdx = videoBuffer[pixel] >> 4;
}
for (uint8_t xoff=0; xoff<2; xoff++) {
for (uint8_t yoff=0; yoff<2; yoff++) {
// FIXME: validate BPP >= 3?
SDL_SetRenderDrawColor(renderer, loresPixelColors[colorIdx][0], loresPixelColors[colorIdx][1], loresPixelColors[colorIdx][2], 255);
SDL_RenderDrawPoint(renderer, x*2+xoff+BASEX, y*2+yoff+BASEY);
}
}
}
}
if (overlayMessage[0]) {
drawString(M_SELECTDISABLED, 1, 240 - 16 - 12, overlayMessage);
}
SDL_RenderPresent(renderer);
}
inline void putpixel(SDL_Renderer *renderer, int x, int y, uint8_t r, uint8_t g, uint8_t b)
{
SDL_SetRenderDrawColor(renderer, r, g, b, 255);
SDL_RenderDrawPoint(renderer, x, y);
}
void SDLDisplay::drawPixel(uint16_t x, uint8_t y, uint16_t color)
{
uint8_t
r = (color & 0xF800) >> 8,
g = (color & 0x7E0) >> 3,
b = (color & 0x1F) << 3;
// Pixel-doubling
for (int yoff=0; yoff<2; yoff++) {
for (int xoff=0; xoff<2; xoff++) {
putpixel(renderer, xoff+x*2, yoff+y*2, r, g, b);
}
}
}
void SDLDisplay::drawPixel(uint16_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b)
{
// Pixel-doubling
for (int yoff=0; yoff<2; yoff++) {
for (int xoff=0; xoff<2; xoff++) {
putpixel(renderer, xoff+x*2, yoff+y*2, r, g, b);
}
}
}
void SDLDisplay::drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c)
{
int8_t xsize = 8,
ysize = 0x0C,
offset = 0x20;
uint16_t temp;
c -= offset;// font starts with a space
uint16_t offPixel, onPixel;
switch (mode) {
case M_NORMAL:
onPixel = 0xFFFF;
offPixel = 0x0010;
break;
case M_SELECTED:
onPixel = 0x0000;
offPixel = 0xFFFF;
break;
case M_DISABLED:
default:
onPixel = 0x7BEF;
offPixel = 0x0000;
break;
case M_SELECTDISABLED:
onPixel = 0x7BEF;
offPixel = 0xFFE0;
break;
}
temp=(c*ysize);
for (int8_t y_off = 0; y_off <= ysize; y_off++) {
uint8_t ch = BiosFont[temp];
for (int8_t x_off = 0; x_off <= xsize; x_off++) {
if (ch & (1 << (7-x_off))) {
drawPixel(x + x_off, y + y_off, onPixel);
} else {
drawPixel(x + x_off, y + y_off, offPixel);
}
}
temp++;
}
}
void SDLDisplay::drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str)
{
int8_t xsize = 8; // width of a char in this font
for (int8_t i=0; i<strlen(str); i++) {
drawCharacter(mode, x, y, str[i]);
x += xsize; // fixme: any inter-char spacing?
}
}
void SDLDisplay::debugMsg(const char *msg)
{
printf("%s\n", msg);
}

42
sdl/sdl-display.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef __SDL_DISPLAY_H
#define __SDL_DISPLAY_H
#include <stdlib.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_events.h>
#include "physicaldisplay.h"
enum {
M_NORMAL = 0,
M_SELECTED = 1,
M_DISABLED = 2,
M_SELECTDISABLED = 3
};
class SDLDisplay : public PhysicalDisplay {
public:
SDLDisplay();
virtual ~SDLDisplay();
virtual void blit(AiieRect r);
virtual void redraw();
virtual void drawDriveDoor(uint8_t which, bool isOpen);
virtual void drawDriveStatus(uint8_t which, bool isRunning);
virtual void drawBatteryStatus(uint8_t percent);
void drawPixel(uint16_t x, uint8_t y, uint16_t color);
void drawPixel(uint16_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b);
virtual void drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c);
virtual void drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str);
virtual void debugMsg(const char *msg);
private:
SDL_Window *screen;
SDL_Renderer *renderer;
};
#endif

189
sdl/sdl-filemanager.cpp Normal file
View File

@ -0,0 +1,189 @@
#include <string.h> // strcpy
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include "sdl-filemanager.h"
SDLFileManager::SDLFileManager()
{
numCached = 0;
}
SDLFileManager::~SDLFileManager()
{
}
int8_t SDLFileManager::openFile(const char *name)
{
// See if there's a hole to re-use...
for (int i=0; i<numCached; i++) {
if (cachedNames[i][0] == '\0') {
strncpy(cachedNames[i], name, MAXPATH-1);
cachedNames[i][MAXPATH-1] = '\0'; // safety: ensure string terminator
fileSeekPositions[i] = 0;
return i;
}
}
// check for too many open files
if (numCached >= MAXFILES)
return -1;
// No, so we'll add it to the end
strncpy(cachedNames[numCached], name, MAXPATH-1);
cachedNames[numCached][MAXPATH-1] = '\0'; // safety: ensure string terminator
fileSeekPositions[numCached] = 0;
numCached++;
return numCached-1;
}
void SDLFileManager::closeFile(int8_t fd)
{
// invalid fd provided?
if (fd < 0 || fd >= numCached)
return;
// clear the name
cachedNames[fd][0] = '\0';
}
const char *SDLFileManager::fileName(int8_t fd)
{
if (fd < 0 || fd >= numCached)
return NULL;
return cachedNames[fd];
}
int8_t SDLFileManager::readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx, uint16_t maxlen)
{
// not used in this version
return -1;
}
void SDLFileManager::seekBlock(int8_t fd, uint16_t block, bool isNib)
{
if (fd < 0 || fd >= numCached)
return;
if (isNib) {
fileSeekPositions[fd] = block * 416;
} else {
fileSeekPositions[fd] = block * 256;
}
}
bool SDLFileManager::readTrack(int8_t fd, uint8_t *toWhere, bool isNib)
{
if (fd < 0 || fd >= numCached)
return false;
if (cachedNames[fd][0] == 0)
return false;
// open, seek, read, close.
bool ret = false;
int ffd = open(cachedNames[fd], O_RDONLY);
if (ffd) {
lseek(ffd, fileSeekPositions[fd], SEEK_SET);
if (isNib) {
ret = (read(ffd, toWhere, 0x1A00) == 0x1A00);
} else {
ret = (read(ffd, toWhere, 256 * 16) == 256 * 16);
}
close(ffd);
}
return ret;
}
bool SDLFileManager::readBlock(int8_t fd, uint8_t *toWhere, bool isNib)
{
// open, seek, read, close.
if (fd < 0 || fd >= numCached)
return false;
if (cachedNames[fd][0] == 0)
return false;
// open, seek, read, close.
bool ret = false;
int ffd = open(cachedNames[fd], O_RDONLY);
if (ffd) {
lseek(ffd, fileSeekPositions[fd], SEEK_SET);
if (isNib) {
ret = (read(ffd, toWhere, 416) == 416);
} else {
ret = (read(ffd, toWhere, 256) == 256);
}
close(ffd);
}
return ret;
}
bool SDLFileManager::writeBlock(int8_t fd, uint8_t *fromWhere, bool isNib)
{
// open, seek, write, close.
if (fd < 0 || fd >= numCached)
return false;
if (cachedNames[fd][0] == 0)
return false;
// don't know how to do this without seeking through the nibblized
// track data, so just give up for now
if (isNib)
return false;
// open, seek, write, close.
int ffd = open(cachedNames[fd], O_WRONLY);
if (ffd) {
if (lseek(ffd, fileSeekPositions[fd], SEEK_SET) != fileSeekPositions[fd]) {
printf("ERROR: failed to seek to %lu\n", fileSeekPositions[fd]);
return false;
}
if (write(ffd, fromWhere, 256) != 256) {
printf("ERROR: failed to write 256 bytes\n");
return false;
}
close(ffd);
}
return true;
}
bool SDLFileManager::writeTrack(int8_t fd, uint8_t *fromWhere, bool isNib)
{
// open, seek, write, close.
if (fd < 0 || fd >= numCached)
return false;
if (cachedNames[fd][0] == 0)
return false;
// open, seek, write, close.
int ffd = open(cachedNames[fd], O_WRONLY);
if (ffd) {
if (lseek(ffd, fileSeekPositions[fd], SEEK_SET) != fileSeekPositions[fd]) {
printf("ERROR: failed to seek to %lu\n", fileSeekPositions[fd]);
return false;
}
int16_t wrsize = 256 * 16;
if (isNib)
wrsize = 0x1A00;
if (write(ffd, fromWhere, wrsize) != wrsize) {
printf("ERROR: failed to write bytes\n");
return false;
}
close(ffd);
}
return true;
}

31
sdl/sdl-filemanager.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef __SDL_FILEMANAGER_H
#define __SDL_FILEMANAGER_H
#include "filemanager.h"
#include <stdint.h>
class SDLFileManager : public FileManager {
public:
SDLFileManager();
virtual ~SDLFileManager();
virtual int8_t openFile(const char *name);
virtual void closeFile(int8_t fd);
virtual const char *fileName(int8_t fd);
virtual int8_t readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx, uint16_t maxlen);
virtual void seekBlock(int8_t fd, uint16_t block, bool isNib = false);
virtual bool readTrack(int8_t fd, uint8_t *toWhere, bool isNib = false);
virtual bool readBlock(int8_t fd, uint8_t *toWhere, bool isNib = false);
virtual bool writeBlock(int8_t fd, uint8_t *fromWhere, bool isNib = false);
virtual bool writeTrack(int8_t fd, uint8_t *fromWhere, bool isNib = false);
private:
int8_t numCached;
char cachedNames[MAXFILES][MAXPATH];
unsigned long fileSeekPositions[MAXFILES];
};
#endif

157
sdl/sdl-keyboard.cpp Normal file
View File

@ -0,0 +1,157 @@
#include "sdl-keyboard.h"
#include "sdl-paddles.h"
#include "globals.h"
SDLKeyboard::SDLKeyboard(VMKeyboard *k) : PhysicalKeyboard(k)
{
}
SDLKeyboard::~SDLKeyboard()
{
}
typedef struct {
int8_t actualKey;
bool shifted;
} keymapChar;
void SDLKeyboard::handleKeypress(SDL_KeyboardEvent *key)
{
bool releaseEvent = key->type == SDL_KEYUP;
if ( (key->keysym.sym >= 'a' && key->keysym.sym <= 'z') ||
(key->keysym.sym >= '0' && key->keysym.sym <= '9') ||
key->keysym.sym == '-' ||
key->keysym.sym == '=' ||
key->keysym.sym == '[' ||
key->keysym.sym == '`' ||
key->keysym.sym == ']' ||
key->keysym.sym == '\\' ||
key->keysym.sym == ';' ||
key->keysym.sym == '\'' ||
key->keysym.sym == ',' ||
key->keysym.sym == '.' ||
key->keysym.sym == '/' ||
key->keysym.sym == ' ' ||
key->keysym.sym == 27 || // ESC
key->keysym.sym == 13 || // return
key->keysym.sym == 9) { // tab
// Simple keypresses
if (key->keysym.mod & (KMOD_LCTRL|KMOD_RCTRL)) {
key->keysym.sym -= ('a'-1);
}
if (releaseEvent)
vmkeyboard->keyReleased(key->keysym.sym);
else
vmkeyboard->keyDepressed(key->keysym.sym);
return;
}
// delete key
if (key->keysym.sym == 8) {
if (releaseEvent)
vmkeyboard->keyReleased(DEL);
else
vmkeyboard->keyDepressed(DEL);
return;
}
//modifier handling
if (key->keysym.sym == SDLK_CAPSLOCK) {
if (releaseEvent)
vmkeyboard->keyReleased(LOCK);
else
vmkeyboard->keyDepressed(LOCK);
}
if (key->keysym.sym == SDLK_LSHIFT ||
key->keysym.sym == SDLK_RSHIFT) {
if (releaseEvent)
vmkeyboard->keyReleased(LSHFT);
else
vmkeyboard->keyDepressed(LSHFT);
}
// arrows
if (key->keysym.sym == SDLK_LEFT) {
if (releaseEvent)
vmkeyboard->keyReleased(LARR);
else
vmkeyboard->keyDepressed(LARR);
}
if (key->keysym.sym == SDLK_RIGHT) {
if (releaseEvent)
vmkeyboard->keyReleased(RARR);
else
vmkeyboard->keyDepressed(RARR);
}
if (key->keysym.sym == SDLK_LEFT) {
if (releaseEvent)
vmkeyboard->keyReleased(LARR);
else
vmkeyboard->keyDepressed(LARR);
}
if (key->keysym.sym == SDLK_UP) {
if (releaseEvent)
vmkeyboard->keyReleased(UARR);
else
vmkeyboard->keyDepressed(UARR);
}
if (key->keysym.sym == SDLK_DOWN) {
if (releaseEvent)
vmkeyboard->keyReleased(DARR);
else
vmkeyboard->keyDepressed(DARR);
}
// Paddles
if (key->keysym.sym == SDLK_LGUI) {
if (releaseEvent)
vmkeyboard->keyReleased(LA);
else
vmkeyboard->keyDepressed(LA);
}
if (key->keysym.sym == SDLK_RGUI) {
if (releaseEvent)
vmkeyboard->keyReleased(RA);
else
vmkeyboard->keyDepressed(RA);
}
}
void SDLKeyboard::maintainKeyboard()
{
SDL_Event event;
if (SDL_PollEvent( &event )) {
// Handle keydown/keyup (and quit, incidentally)
switch (event.type) {
case SDL_KEYDOWN:
case SDL_KEYUP:
// Don't handle repeats; we have our own repeat code
if (event.key.repeat == 0)
handleKeypress(&event.key);
break;
case SDL_MOUSEMOTION:
// We are handling the SDL input loop, so need to pass this off to the paddles. :/
// FIXME: nasty rooting around in other objects and typecasting.
// FIXME: event.motion.state & SDL_BUTTON_LMASK, et al?
((SDLPaddles *)g_paddles)->gotMouseMovement(event.motion.x, event.motion.y);
break;
case SDL_QUIT:
exit(0);
}
}
}

20
sdl/sdl-keyboard.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef __SDL_KEYBOARD_H
#define __SDL_KEYBOARD_H
#include "physicalkeyboard.h"
#include "vmkeyboard.h"
#include <SDL2/SDL.h>
class SDLKeyboard : public PhysicalKeyboard {
public:
SDLKeyboard(VMKeyboard *k);
virtual ~SDLKeyboard();
virtual void maintainKeyboard();
private:
void handleKeypress(SDL_KeyboardEvent *key);
};
#endif

40
sdl/sdl-paddles.cpp Normal file
View File

@ -0,0 +1,40 @@
#include <stdio.h>
#include "sdl-paddles.h"
// FIXME: abstract this somewhere
#define WINDOWHEIGHT (240*2)
#define WINDOWWIDTH (320*2)
#include "globals.h"
SDLPaddles::SDLPaddles()
{
p0 = p1 = 127;
}
SDLPaddles::~SDLPaddles()
{
}
void SDLPaddles::startReading()
{
g_vm->triggerPaddleInCycles(0, 12 * p0);
g_vm->triggerPaddleInCycles(1, 12 * p1);
}
uint8_t SDLPaddles::paddle0()
{
return p0;
}
uint8_t SDLPaddles::paddle1()
{
return p1;
}
void SDLPaddles::gotMouseMovement(uint16_t x, uint16_t y)
{
p0 = ((float)x / (float)WINDOWWIDTH) * (float) 255.0;
p1 = ((float)y / (float)WINDOWHEIGHT) * (float) 255.0;
}

19
sdl/sdl-paddles.h Normal file
View File

@ -0,0 +1,19 @@
#include <stdint.h>
#include "physicalpaddles.h"
class SDLPaddles : public PhysicalPaddles {
public:
SDLPaddles();
virtual ~SDLPaddles();
virtual void startReading();
virtual uint8_t paddle0();
virtual uint8_t paddle1();
void gotMouseMovement(uint16_t x, uint16_t y);
public:
uint8_t p0;
uint8_t p1;
};

92
sdl/sdl-printer.cpp Normal file
View File

@ -0,0 +1,92 @@
#include "sdl-printer.h"
#define WINDOWNAME "printer"
inline void putpixel(SDL_Renderer *renderer, int x, int y, uint8_t d)
{
uint8_t v = (d ? 0xFF : 0x00);
SDL_SetRenderDrawColor(renderer, v, v, v, 255);
SDL_RenderDrawPoint(renderer, x, y);
}
SDLPrinter::SDLPrinter()
{
ypos = 0;
isDirty = false;
memset((void *)_hackyBitmap, 0, sizeof(_hackyBitmap));
window = SDL_CreateWindow(WINDOWNAME,
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
WIDTH, HEIGHT,
SDL_WINDOW_SHOWN);
// SDL_RENDERER_SOFTWARE because, at least on my Mac, this has some
// serious issues with hardware accelerated drawing (flashing and
// crashing).
renderer = SDL_CreateRenderer(window, -1, 0);
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
SDL_RenderClear(renderer); // clear it to the selected color
SDL_RenderPresent(renderer); // perform the render
}
SDLPrinter::~SDLPrinter()
{
}
void SDLPrinter::update()
{
if (isDirty) {
isDirty = false; // set early in case there's a race
for (int y=0; y<HEIGHT; y++) {
for (int x=0; x<WIDTH; x++) {
if (_hackyBitmap[y*WIDTH+x]) {
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
} else {
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
}
SDL_RenderDrawPoint(renderer, x, y);
}
}
SDL_RenderPresent(renderer);
}
}
void SDLPrinter::addLine(uint8_t *rowOfBits)
{
for (int yoff=0; yoff<9; yoff++) {
// 960 pixels == 120 bytes -- FIXME
for (int i=0; i<(NATIVEWIDTH/8); i++) {
uint8_t bv = rowOfBits[yoff*120+i];
for (int xoff=0; xoff<8; xoff++) {
// scale X from "actual FX80" coordinates to "real printer" coordinates
uint16_t actualX = (uint16_t)(((float)(i*8+xoff) * (float)WIDTH) / (float)NATIVEWIDTH);
uint8_t pixelColor = (bv & (1 << (7-xoff))) ? 0xFF : 0x00;
// Make sure to preserve any pixels we've already drawn b/c scaling & overstrike...
_hackyBitmap[actualX + ((ypos+yoff)%HEIGHT) * WIDTH] |= pixelColor;
}
}
}
if (ypos >= HEIGHT) {
ypos = 0;
}
isDirty = true;
}
void SDLPrinter::moveDownPixels(uint8_t p)
{
ypos+= p;
if (ypos >= HEIGHT) {
// clear page & restart
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderClear(renderer);
isDirty = true;
ypos = 0;
}
}

41
sdl/sdl-printer.h Normal file
View File

@ -0,0 +1,41 @@
#ifndef __SDL_PRINTER_H
#define __SDL_PRINTER_H
#include <stdlib.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_events.h>
#include "physicalprinter.h"
#define HEIGHT 800
#define NATIVEWIDTH 960 // FIXME: printer can change density...
//#define WIDTH 384 // emulating the teeny printer I've got
#define WIDTH 960
class SDLPrinter : public PhysicalPrinter {
public:
SDLPrinter();
virtual ~SDLPrinter();
virtual void addLine(uint8_t *rowOfBits); // must be 960 pixels wide (120 bytes)
virtual void update();
virtual void moveDownPixels(uint8_t p);
private:
bool isDirty;
uint16_t ypos;
SDL_Window *window;
SDL_Renderer *renderer;
bool _hackyBitmap[WIDTH * HEIGHT];
};
#endif

157
sdl/sdl-speaker.cpp Normal file
View File

@ -0,0 +1,157 @@
#include "sdl-speaker.h"
#include <pthread.h>
extern "C"
{
#include <SDL.h>
#include <SDL_thread.h>
};
#include "timeutil.h"
// FIXME: Globals; ick.
static pthread_t speakerThreadID;
static uint8_t curSpeakerData = 0x00;
static volatile uint16_t bufIdx = 0;
static uint8_t soundBuf[4096];
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static uint64_t hitcount;
static uint64_t misscount;
static uint64_t copycount = 0;
static void audioCallback(void *unused, Uint8 *stream, int len)
{
pthread_mutex_lock(&mutex);
if (bufIdx >= len) {
memcpy(stream, soundBuf, len);
if (bufIdx > len) {
// move the remaining data down
memcpy(soundBuf, &soundBuf[len], bufIdx - len);
bufIdx -= len;
copycount += len;
}
} else {
// Audio underrun
memset(stream, 0, len);
}
pthread_mutex_unlock(&mutex);
}
static void *speaker_thread(void *dummyptr) {
struct timespec currentTime;
struct timespec startTime;
struct timespec nextSampleTime;
pthread_mutex_init(&mutex, NULL);
SDL_AudioSpec audioDevice;
SDL_AudioSpec audioActual;
SDL_memset(&audioDevice, 0, sizeof(audioDevice));
audioDevice.freq = 22050;
audioDevice.format = AUDIO_U8;
audioDevice.channels = 1;
audioDevice.samples = 2048; // 2048 bytes @ 22050Hz is about 1/10th second out of sync - should be okay for this testing
audioDevice.callback = audioCallback;
audioDevice.userdata = NULL;
SDL_OpenAudio(&audioDevice, &audioActual); // FIXME retval
printf("Actual: freq %d channels %d samples %d\n",
audioActual.freq, audioActual.channels, audioActual.samples);
_init_darwin_shim();
do_gettime(&startTime);
do_gettime(&nextSampleTime);
SDL_PauseAudio(0);
uint64_t sampleCount = 0;
while (1) {
do_gettime(&currentTime);
struct timespec diff = tsSubtract(nextSampleTime, currentTime);
if (diff.tv_sec >= 0 && diff.tv_nsec >= 0) {
nanosleep(&diff, NULL);
hitcount++;
} else
misscount++;
if ((sampleCount & 0xFFFF) == 0) {
printf("sound hit: %lld miss: %lld copy: %lld\n", hitcount, misscount, copycount);
}
pthread_mutex_lock(&mutex);
soundBuf[bufIdx++] = curSpeakerData & 0xFF;
if (bufIdx >= sizeof(soundBuf)) {
// Audio overrun; start dropping data
bufIdx--;
}
pthread_mutex_unlock(&mutex);
// set nextSampleTime to the absolute reference time of when the
// next sample should start (based on our start time).
timespec_add_us(&startTime, (sampleCount * 1000000) / 22050 , &nextSampleTime);
sampleCount++;
}
}
SDLSpeaker::SDLSpeaker()
{
toggleState = false;
needsToggle = false;
mixerValue = 0;
_init_darwin_shim(); // set up the clock interface
if (!pthread_create(&speakerThreadID, NULL, &speaker_thread, (void *)NULL)) {
printf("speaker thread created\n");
}
}
SDLSpeaker::~SDLSpeaker()
{
pclose(f);
}
void SDLSpeaker::toggle()
{
needsToggle = true;
}
void SDLSpeaker::maintainSpeaker(uint32_t c)
{
if (needsToggle) {
// Override the mixer with a 1-bit "Terribad" audio sample change
toggleState = !toggleState;
needsToggle = false;
}
// The "terribad" audio gets two shares. THis means we've got 8 total
// "voices" -- 6 from the mockingboard and 2 from the built-in. 8 is
// a great number for dividing. :)
mixerValue += (toggleState ? 0xFF : 0x00);
numMixed += 2;
#if DEBUGGING
if (numMixed != 8) {
printf("SPEAKER FAIL - should always be 8\n");
}
#endif
mixerValue >>= 3; // divide by 8
curSpeakerData = mixerValue & 0xFF;
}
void SDLSpeaker::beginMixing()
{
mixerValue = 0;
numMixed = 0;
}
void SDLSpeaker::mixOutput(uint8_t v)
{
mixerValue += v;
numMixed++;
}

26
sdl/sdl-speaker.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef __SDLSPEAKER_H
#define __SDLSPEAKER_H
#include <stdio.h>
#include <stdint.h>
#include "physicalspeaker.h"
class SDLSpeaker : public PhysicalSpeaker {
public:
SDLSpeaker();
virtual ~SDLSpeaker();
virtual void toggle();
virtual void maintainSpeaker(uint32_t c);
virtual void beginMixing();
virtual void mixOutput(uint8_t v);
private:
uint32_t mixerValue;
uint8_t numMixed;
bool toggleState;
bool needsToggle;
FILE *f;
};
#endif

143
sdl/timeutil.h Normal file
View File

@ -0,0 +1,143 @@
#include <time.h>
#include <mach/mach_time.h>
// Derived from
// http://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x
#define ORWL_NANO (+1.0E-9)
#define ORWL_GIGA UINT64_C(1000000000)
#define NANOSECONDS_PER_SECOND 1000000000UL
#define CYCLES_PER_SECOND 1023000UL
#define NANOSECONDS_PER_CYCLE (NANOSECONDS_PER_SECOND / CYCLES_PER_SECOND)
static double orwl_timebase = 0.0;
static uint64_t orwl_timestart = 0;
static void _init_darwin_shim(void) {
mach_timebase_info_data_t tb = { 0 };
mach_timebase_info(&tb);
orwl_timebase = tb.numer;
orwl_timebase /= tb.denom;
orwl_timestart = mach_absolute_time();
}
static int do_gettime(struct timespec *tp) {
double diff = (mach_absolute_time() - orwl_timestart) * orwl_timebase;
tp->tv_sec = diff * ORWL_NANO;
tp->tv_nsec = diff - (tp->tv_sec * ORWL_GIGA);
return 0;
}
// adds the number of microseconds that 'cycles' takes to *start and
// returns it in *out
static void timespec_add_cycles(struct timespec *start,
uint32_t cycles,
struct timespec *out)
{
out->tv_sec = start->tv_sec;
out->tv_nsec = start->tv_nsec;
uint64_t nanosToAdd = NANOSECONDS_PER_CYCLE * cycles;
out->tv_sec += (nanosToAdd / NANOSECONDS_PER_SECOND);
out->tv_nsec += (nanosToAdd % NANOSECONDS_PER_SECOND);
if (out->tv_nsec >= 1000000000L) {
out->tv_sec++ ;
out->tv_nsec -= 1000000000L;
}
}
// adds the number of microseconds given to *start and
// returns it in *out
static void timespec_add_us(struct timespec *start,
uint64_t micros,
struct timespec *out)
{
out->tv_sec = start->tv_sec;
out->tv_nsec = start->tv_nsec;
uint64_t nanosToAdd = micros * 1000L;
out->tv_sec += (nanosToAdd / NANOSECONDS_PER_SECOND);
out->tv_nsec += (nanosToAdd % NANOSECONDS_PER_SECOND);
if (out->tv_nsec >= 1000000000L) {
out->tv_sec++ ;
out->tv_nsec -= 1000000000L;
}
}
static void timespec_diff(struct timespec *start,
struct timespec *end,
struct timespec *diff,
bool *negative) {
struct timespec t;
if (negative)
{
*negative = false;
}
// if start > end, swizzle...
if ( (start->tv_sec > end->tv_sec) || ((start->tv_sec == end->tv_sec) && (start->tv_nsec > end->tv_nsec)) )
{
t=*start;
*start=*end;
*end=t;
if (negative)
{
*negative = true;
}
}
// assuming time_t is signed ...
if (end->tv_nsec < start->tv_nsec)
{
t.tv_sec = end->tv_sec - start->tv_sec - 1;
t.tv_nsec = 1000000000 + end->tv_nsec - start->tv_nsec;
}
else
{
t.tv_sec = end->tv_sec - start->tv_sec;
t.tv_nsec = end->tv_nsec - start->tv_nsec;
}
diff->tv_sec = t.tv_sec;
diff->tv_nsec = t.tv_nsec;
}
// tsCompare: return -1, 0, 1 for (a < b), (a == b), (a > b)
static int8_t tsCompare(struct timespec *A, struct timespec *B)
{
if (A->tv_sec < B->tv_sec)
return -1;
if (A->tv_sec > B->tv_sec)
return 1;
if (A->tv_nsec < B->tv_nsec)
return -1;
if (A->tv_nsec > B->tv_nsec)
return 1;
return 0;
}
static struct timespec tsSubtract(struct timespec time1, struct timespec time2)
{
struct timespec result;
if ((time1.tv_sec < time2.tv_sec) ||
((time1.tv_sec == time2.tv_sec) &&
(time1.tv_nsec <= time2.tv_nsec))) {/* TIME1 <= TIME2? */
result.tv_sec = result.tv_nsec = 0 ;
} else {/* TIME1 > TIME2 */
result.tv_sec = time1.tv_sec - time2.tv_sec ;
if (time1.tv_nsec < time2.tv_nsec) {
result.tv_nsec = time1.tv_nsec + 1000000000L - time2.tv_nsec ;
result.tv_sec-- ;/* Borrow a second. */
} else {
result.tv_nsec = time1.tv_nsec - time2.tv_nsec ;
}
}
return (result) ;
}

1
teensy/ay8910.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/ay8910.cpp

1
teensy/ay8910.h Symbolic link
View File

@ -0,0 +1 @@
../apple/ay8910.h

View File

@ -98,6 +98,7 @@ bool BIOS::runUntilDone()
case ACT_DISPLAYTYPE:
g_displayType++;
g_displayType %= 4; // FIXME: abstract max #
((AppleDisplay*)g_display)->displayTypeChanged();
break;
case ACT_DEBUG:
debugMode++;
@ -143,7 +144,7 @@ bool BIOS::runUntilDone()
done:
// Undo whatever damage we've done to the screen
g_display->redraw();
g_display->blit();
g_display->blit({0, 0, 279, 191});
// return true if any persistent setting changed that we want to store in eeprom
return volumeDidChange;

1
teensy/mockingboard.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/mockingboard.cpp

1
teensy/mockingboard.h Symbolic link
View File

@ -0,0 +1 @@
../apple/mockingboard.h

1
teensy/sy6522.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/sy6522.cpp

1
teensy/sy6522.h Symbolic link
View File

@ -0,0 +1 @@
../apple/sy6522.h

View File

@ -366,7 +366,7 @@ void TeensyDisplay::drawNextPixel(uint16_t color)
setPixel(color);
}
void TeensyDisplay::blit()
void TeensyDisplay::blit(AiieRect r)
{
uint8_t *videoBuffer = g_vm->videoBuffer; // FIXME: poking deep
@ -375,12 +375,12 @@ void TeensyDisplay::blit()
#define VOFFSET 13
// Define the horizontal area that we're going to draw in
LCD_Write_COM_DATA(0x45, HOFFSET); // offset by 20 to center it...
LCD_Write_COM_DATA(0x46, 279+HOFFSET);
LCD_Write_COM_DATA(0x45, HOFFSET+r.left); // offset by 20 to center it...
LCD_Write_COM_DATA(0x46, HOFFSET+r.right);
// position the "write" address
LCD_Write_COM_DATA(0x4e,0+VOFFSET); // row
LCD_Write_COM_DATA(0x4f,HOFFSET); // col
LCD_Write_COM_DATA(0x4e,VOFFSET+r.top); // row
LCD_Write_COM_DATA(0x4f,HOFFSET+r.left); // col
// prepare the LCD to receive data bytes for its RAM
LCD_Write_COM(0x22);
@ -388,12 +388,11 @@ void TeensyDisplay::blit()
// send the pixel data
sbi(P_RS, B_RS);
uint16_t pixel;
for (uint8_t y=0; y<192; y++) { // Drawing 192 of the 240 rows
pixel = y * (320/2)-1;
for (uint16_t x=0; x<280; x++) { // Drawing 280 of the 320 pixels
for (uint8_t y=r.top; y<=r.bottom; y++) {
for (uint16_t x=r.left; x<=r.right; x++) {
pixel = y * (DISPLAYWIDTH/2) + (x/2);
uint8_t colorIdx;
if (!(x & 0x01)) {
pixel++;
colorIdx = videoBuffer[pixel] >> 4;
} else {
colorIdx = videoBuffer[pixel] & 0x0F;

View File

@ -34,7 +34,7 @@ class TeensyDisplay : public PhysicalDisplay {
TeensyDisplay();
virtual ~TeensyDisplay();
virtual void blit();
virtual void blit(AiieRect r);
virtual void redraw();
void clrScr();

View File

@ -228,4 +228,11 @@ void TeensyKeyboard::maintainKeyboard()
}
}
}
// For debugging: also allow USB serial to act as a keyboard
if (Serial.available()) {
int c = Serial.read();
vmkeyboard->keyDepressed(c);
vmkeyboard->keyReleased(c);
}
}

View File

@ -9,6 +9,8 @@
TeensyPrinter::TeensyPrinter()
{
#if 0
// debugging
ser = new SoftwareSerial(RXPIN, TXPIN, false);
ser->begin(19200);
char buf[6] = { 27, '@', // init command '@'
@ -16,6 +18,7 @@ TeensyPrinter::TeensyPrinter()
0 // terminator
};
ser->print(buf);
#endif
}
TeensyPrinter::~TeensyPrinter()
@ -29,6 +32,9 @@ void TeensyPrinter::update()
void TeensyPrinter::addLine(uint8_t *rowOfBits)
{
if (!ser)
return;
static uint8_t linebuf[WIDTH/8]; // output data for one line of pixels
// The rowOfBits is a set of *rows* of bits. The printer needs *columns*.

View File

@ -5,34 +5,50 @@ extern int16_t g_volume;
TeensySpeaker::TeensySpeaker(uint8_t pinNum) : PhysicalSpeaker()
{
speakerState = false;
toggleState = false;
needsToggle = false;
speakerPin = pinNum;
pinMode(speakerPin, OUTPUT); // analog speaker output, used as digital volume control
nextTransitionAt = 0;
mixerValue = numMixed = 0;
}
TeensySpeaker::~TeensySpeaker()
{
}
void TeensySpeaker::toggleAtCycle(uint32_t c)
void TeensySpeaker::toggle()
{
// FIXME: could tell here if we dropped something - is nextTransitionAt already set? If so, we missed a toggle :(
nextTransitionAt = c;
needsToggle = true;
}
void TeensySpeaker::maintainSpeaker(uint32_t c)
{
if (nextTransitionAt && c >= nextTransitionAt) {
nextTransitionAt = 0;
speakerState = !speakerState;
// FIXME: glad it's DAC0 and all, but... how does that relate to the pin passed in the constructor?
analogWriteDAC0(speakerState ? g_volume : 0); // max: 4095
if (needsToggle) {
toggleState = !toggleState;
needsToggle = false;
}
mixerValue += (toggleState ? 0xFFF : 0x00);
// FIXME: Temporarily disabling mixer
/* numMixed += 2;
if (numMixed > 1) {
mixerValue /= numMixed;
}*/
// FIXME: glad it's DAC0 and all, but... how does that relate to the pin passed in the constructor?
analogWriteDAC0(mixerValue); // FIXME: g_volume?
}
bool TeensySpeaker::currentState()
void TeensySpeaker::beginMixing()
{
return speakerState;
mixerValue = 0;
numMixed = 0;
}
void TeensySpeaker::mixOutput(uint8_t v)
{
mixerValue += v;
numMixed++;
}

View File

@ -8,16 +8,20 @@ class TeensySpeaker : public PhysicalSpeaker {
TeensySpeaker(uint8_t pinNum);
virtual ~TeensySpeaker();
virtual void toggleAtCycle(uint32_t c);
virtual void toggle();
virtual void maintainSpeaker(uint32_t c);
virtual bool currentState();
virtual void beginMixing();
virtual void mixOutput(uint8_t v);
private:
bool speakerState;
uint8_t speakerPin;
uint32_t nextTransitionAt;
bool toggleState;
bool needsToggle;
uint32_t mixerValue;
uint8_t numMixed;
};
#endif

View File

@ -1,5 +1,4 @@
#include <Arduino.h>
#include <TimerOne.h>
#include <ff.h> // uSDFS
#include <SPI.h>
#include <EEPROM.h>
@ -17,8 +16,6 @@
#define BATTERYPIN A19
#define SPEAKERPIN A21
//#define DEBUGCPU
#include "globals.h"
#include "teensy-crash.h"
@ -28,7 +25,7 @@ volatile float startMicros;
FATFS fatfs; /* File system object */
BIOS bios;
uint8_t videoBuffer[320*240/2];
uint8_t videoBuffer[DISPLAYWIDTH*DISPLAYHEIGHT/2];
enum {
D_NONE = 0,
@ -100,7 +97,8 @@ void setup()
analogReference(EXTERNAL); // 3.3v external, instead of 1.7v internal
analogReadRes(8); // We only need 8 bits of resolution (0-255) for battery & paddles
analogReadAveraging(4); // ?? dunno if we need this or not.
analogWriteResolution(12);
pinMode(SPEAKERPIN, OUTPUT); // analog speaker output, used as digital volume control
pinMode(BATTERYPIN, INPUT);
@ -146,7 +144,7 @@ void setup()
g_vm->Reset();
g_display->redraw();
g_display->blit();
// g_display->blit();
Serial.println("Reading prefs");
readPrefs(); // read from eeprom and set anything we need setting
@ -155,73 +153,14 @@ void setup()
startMicros = 0;
nextInstructionMicros = micros();
Timer1.initialize(3);
Timer1.attachInterrupt(runCPU);
Timer1.start();
}
/* We're running the timer that calls this at 1/3 "normal" speed, and
* then asking runCPU to run 48 steps (individual opcodes executed) of
* the CPU before returning. Then we figure out how many cycles
* elapsed during that run, and keep track of how many cycles we now
* have to "drain off" (how many extra ran during this attempt -- we
* expected at least 3, but might have gotten more). Then the next
* call here from the interrupt subtracts 3 cycles, on the assumption
* that 3 have passed, and we're good to go.
*
* This approach is reasonable: the 6502 instruction set takes an
* average of 4 clock cycles to execute. This compromise keeps us from
* chewing up the entire CPU on interrupt overhead, allowing us to
* focus on refreshing the LCD as fast as possible while sacrificing
* some small timing differences. Experimentally, paddle values seem
* to still read well up to 48 steps. At 2*48, the paddles drift at
* the low end, meaning there's probably an issue with timing.
*/
void runCPU()
{
// static bool outputState = false;
// outputState = !outputState;
// digitalWrite(56, outputState);
// Debugging: insert a disk on startup...
// ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/UTIL/mock2dem.dsk", false);
((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/JORJ/disk_s6d1.dsk", false);
// ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/GAMES/ALIBABA.DSK", false);
if (micros() >= nextInstructionMicros) {
#ifdef DEBUGCPU
g_cpu->Run(1);
#else
g_cpu->Run(24);
#endif
// The CPU of the Apple //e ran at 1.023 MHz. Adjust when we think
// the next instruction should run based on how long the execution
// was ((1000/1023) * numberOfCycles) - which is about 97.8%.
#ifdef DEBUGCPU
// ... have to slow down so the printing all works
nextInstructionMicros = startMicros + (float)g_cpu->cycles * 50;
#else
nextInstructionMicros = startMicros + (float)g_cpu->cycles * 0.978;
#endif
#ifdef DEBUGCPU
{
uint8_t p = g_cpu->flags;
Serial.printf("OP: $%02x A: %02x X: %02x Y: %02x PC: $%04x SP: %02x Flags: %c%cx%c%c%c%c%c\n",
g_vm->getMMU()->read(g_cpu->pc),
g_cpu->a, g_cpu->x, g_cpu->y, g_cpu->pc, g_cpu->sp,
p & (1<<7) ? 'N':' ',
p & (1<<6) ? 'V':' ',
p & (1<<4) ? 'B':' ',
p & (1<<3) ? 'D':' ',
p & (1<<2) ? 'I':' ',
p & (1<<1) ? 'Z':' ',
p & (1<<0) ? 'C':' '
);
}
#endif
}
g_speaker->beginMixing();
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles);
g_speaker->maintainSpeaker(g_cpu->cycles);
pinMode(56, OUTPUT);
pinMode(57, OUTPUT);
}
// FIXME: move these memory-related functions elsewhere...
@ -252,9 +191,6 @@ int heapSize(){
void biosInterrupt()
{
// Shut down the CPU
Timer1.stop();
// wait for the interrupt button to be released
while (digitalRead(RESETPIN) == LOW)
;
@ -280,18 +216,35 @@ void biosInterrupt()
// Poll the keyboard before we start, so we can do selftest on startup
g_keyboard->maintainKeyboard();
// Restart the CPU
Timer1.start();
}
bool debugState = false;
bool debugLCDState = false;
void loop()
{
static uint16_t ctr = -1;
/* testing the fault handler? uncomment this and it'll crash. */
// *((int*)0x0) = 1;
if (micros() >= nextInstructionMicros) {
debugState = !debugState;
digitalWrite(56, debugState);
g_cpu->Run(24);
// Only update the speaker and CPU when we've moved the CPU forward (or there's nothing to do).
g_speaker->beginMixing();
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles);
g_speaker->maintainSpeaker(g_cpu->cycles);
// The CPU of the Apple //e ran at 1.023 MHz. Adjust when we think
// the next instruction should run based on how long the execution
// was ((1000/1023) * numberOfCycles) - which is about 97.8%.
nextInstructionMicros = startMicros + (float)g_cpu->cycles * 0.978;
// Don't update the LCD if we had to run the CPU: if we need
// multiple concurrent runs to catch up, then we want to catch up.
return;
}
static unsigned long nextBattCheck = 0;
static int batteryLevel = 0; // static for debugging code! When done
@ -320,64 +273,69 @@ void loop()
((AppleVM *)g_vm)->batteryLevel( batteryLevel );
}
if ((++ctr & 0xFF) == 0) {
if (digitalRead(RESETPIN) == LOW) {
// This is the BIOS interrupt. We immediately act on it.
biosInterrupt();
}
if (digitalRead(RESETPIN) == LOW) {
// This is the BIOS interrupt. We immediately act on it.
biosInterrupt();
}
if (g_vm->vmdisplay->needsRedraw()) {
// make sure to clear the flag before drawing; there's no lock
// on didRedraw, so the other thread might update it
g_vm->vmdisplay->didRedraw();
g_display->blit();
}
g_keyboard->maintainKeyboard();
{
char buf[25];
switch (debugMode) {
case D_SHOWFPS:
// display some FPS data
static uint32_t startAt = millis();
static uint32_t loopCount = 0;
loopCount++;
time_t lenSecs;
lenSecs = (millis() - startAt) / 1000;
if (lenSecs >= 5) {
sprintf(buf, "%lu FPS", loopCount / lenSecs);
g_display->debugMsg(buf);
startAt = millis();
loopCount = 0;
}
break;
case D_SHOWMEMFREE:
sprintf(buf, "%lu %u", FreeRamEstimate(), heapSize());
g_display->debugMsg(buf);
break;
case D_SHOWPADDLES:
sprintf(buf, "%u %u", g_paddles->paddle0(), g_paddles->paddle1());
g_display->debugMsg(buf);
break;
case D_SHOWPC:
sprintf(buf, "%X", g_cpu->pc);
g_display->debugMsg(buf);
break;
case D_SHOWCYCLES:
sprintf(buf, "%lX", g_cpu->cycles);
g_display->debugMsg(buf);
break;
case D_SHOWBATTERY:
sprintf(buf, "BAT %d", analogRead(BATTERYPIN));
g_display->debugMsg(buf);
break;
case D_SHOWTIME:
sprintf(buf, "%.2d:%.2d:%.2d", hour(), minute(), second());
g_display->debugMsg(buf);
break;
}
if (g_vm->vmdisplay->needsRedraw()) {
digitalWrite(57, HIGH);
AiieRect what = g_vm->vmdisplay->getDirtyRect();
// Clear the flag before redrawing. Not specifically required
// any longer now that we're not running out of an interrupt,
// but still safe.
g_vm->vmdisplay->didRedraw();
g_display->blit(what);
digitalWrite(57, LOW);
}
g_keyboard->maintainKeyboard();
doDebugging();
}
void doDebugging()
{
char buf[25];
switch (debugMode) {
case D_SHOWFPS:
// display some FPS data
static uint32_t startAt = millis();
static uint32_t loopCount = 0;
loopCount++;
time_t lenSecs;
lenSecs = (millis() - startAt) / 1000;
if (lenSecs >= 5) {
sprintf(buf, "%lu FPS", loopCount / lenSecs);
g_display->debugMsg(buf);
startAt = millis();
loopCount = 0;
}
break;
case D_SHOWMEMFREE:
sprintf(buf, "%lu %u", FreeRamEstimate(), heapSize());
g_display->debugMsg(buf);
break;
case D_SHOWPADDLES:
sprintf(buf, "%u %u", g_paddles->paddle0(), g_paddles->paddle1());
g_display->debugMsg(buf);
break;
case D_SHOWPC:
sprintf(buf, "%X", g_cpu->pc);
g_display->debugMsg(buf);
break;
case D_SHOWCYCLES:
sprintf(buf, "%lX", g_cpu->cycles);
g_display->debugMsg(buf);
break;
case D_SHOWBATTERY:
sprintf(buf, "BAT %d", analogRead(BATTERYPIN));
g_display->debugMsg(buf);
break;
case D_SHOWTIME:
sprintf(buf, "%.2d:%.2d:%.2d", hour(), minute(), second());
g_display->debugMsg(buf);
break;
}
}
@ -419,12 +377,8 @@ void readPrefs()
g_volume = 0;
}
// Writes to EEPROM slow down the Teensy 3.6's CPU to 120MHz automatically. Disable our timer
// while we're doing it and we'll just see a pause.
void writePrefs()
{
Timer1.stop();
Serial.println("writing prefs");
prefs p;
@ -436,6 +390,4 @@ void writePrefs()
for (uint8_t i=0; i<sizeof(prefs); i++) {
EEPROM.write(i, *pp++);
}
Timer1.start();
}

5
vm.h
View File

@ -8,9 +8,12 @@
#include "vmdisplay.h"
#include "vmkeyboard.h"
#define DISPLAYWIDTH 320
#define DISPLAYHEIGHT 240
class VM {
public:
VM() { mmu=NULL; vmdisplay = NULL; videoBuffer = (uint8_t *)calloc(320*240/2, 1); hasIRQ = false;}
VM() { mmu=NULL; vmdisplay = NULL; videoBuffer = (uint8_t *)calloc(DISPLAYWIDTH * DISPLAYHEIGHT / 2, 1); hasIRQ = false;}
virtual ~VM() { if (mmu) delete mmu; if (vmdisplay) delete vmdisplay; free(videoBuffer); }
virtual void SetMMU(MMU *mmu) { this->mmu = mmu; }

View File

@ -3,6 +3,13 @@
class MMU;
typedef struct {
uint8_t top;
uint16_t left;
uint8_t bottom;
uint16_t right;
} AiieRect;
class VMDisplay {
public:
VMDisplay(uint8_t *vb) { videoBuffer = vb; }
@ -12,6 +19,7 @@ class VMDisplay {
virtual bool needsRedraw() = 0;
virtual void didRedraw() = 0;
virtual AiieRect getDirtyRect() = 0;
MMU *mmu;
uint8_t *videoBuffer;