mirror of
https://github.com/JorjBauer/aiie.git
synced 2024-12-03 12:49:21 +00:00
continued work on sound: migrate to SDL for host-based audio debugging
This commit is contained in:
parent
969aa402ad
commit
39fdffd9c4
24
Makefile
24
Makefile
@ -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
|
||||
|
@ -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
|
||||
|
@ -60,7 +60,6 @@ void AppleVM::cpuMaintenance(uint32_t cycles)
|
||||
}
|
||||
|
||||
keyboard->maintainKeyboard(cycles);
|
||||
parallel->update();
|
||||
mockingboard->update(cycles);
|
||||
}
|
||||
|
||||
|
201
apple/ay8910.cpp
201
apple/ay8910.cpp
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,8 +60,6 @@ class Fx80 {
|
||||
|
||||
void input(uint8_t c);
|
||||
|
||||
void update();
|
||||
|
||||
private:
|
||||
void lineFeed();
|
||||
void clearLine();
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -43,8 +43,3 @@ void ParallelCard::loadROM(uint8_t *toWhere)
|
||||
memcpy(toWhere, romData, 256);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ParallelCard::update()
|
||||
{
|
||||
fx80->update();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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(¤tTime);
|
||||
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++;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
297
sdl/aiie.cpp
Normal 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, ¶m);
|
||||
param.sched_priority = sched_get_priority_max(policy);
|
||||
pthread_setschedparam(pthread_self(), policy, ¶m);
|
||||
#endif
|
||||
|
||||
_init_darwin_shim();
|
||||
do_gettime(&startTime);
|
||||
do_gettime(&nextInstructionTime);
|
||||
|
||||
printf("free-running\n");
|
||||
while (1) {
|
||||
// cycle down the CPU...
|
||||
do_gettime(¤tTime);
|
||||
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
274
sdl/sdl-display.cpp
Normal 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
42
sdl/sdl-display.h
Normal 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
189
sdl/sdl-filemanager.cpp
Normal 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';
|
||||
}
|
||||
< |