mirror of
https://github.com/cmosher01/Epple-II.git
synced 2025-02-02 13:31:36 +00:00
rewrite cassette handling: wave files, separate in/out ports
This commit is contained in:
parent
e296b6a6f9
commit
c787dbb80c
@ -11,7 +11,8 @@ epple2_LDFLAGS=$(all_libraries)
|
||||
epple2_CPPFLAGS = $(AM_CPPFLAGS) -DETCDIR=\"$(sysconfdir)\"
|
||||
|
||||
epple2_SOURCES = a2colorsobserved.cpp addressbus.cpp analogtv.cpp apple2.cpp \
|
||||
applentsc.cpp card.cpp cassette.cpp clipboardhandler.cpp clockcard.cpp \
|
||||
applentsc.cpp card.cpp cassette.cpp cassettein.cpp cassetteout.cpp \
|
||||
clipboardhandler.cpp clockcard.cpp \
|
||||
configep2.cpp cpu.cpp diskbytes.cpp diskcontroller.cpp drive.cpp drivemotor.cpp \
|
||||
emptyslot.cpp emulator.cpp firmwarecard.cpp gui.cpp hypermode.cpp \
|
||||
keyboard.cpp keyboardbuffermode.cpp languagecard.cpp lowpass_1_5_mhz.cpp \
|
||||
@ -26,7 +27,8 @@ StateCalculator.cpp Trace.cpp TransCache.cpp TransNetwork.cpp \
|
||||
tinyfiledialogs.cpp
|
||||
|
||||
noinst_HEADERS = a2colorsobserved.h addressbus.h analogtv.h apple2.h applentsc.h \
|
||||
card.h cassette.h clipboardhandler.h clockcard.h configep2.h cpu.h diskbytes.h \
|
||||
card.h cassette.h cassettein.h cassetteout.h \
|
||||
clipboardhandler.h clockcard.h configep2.h cpu.h diskbytes.h \
|
||||
diskcontroller.h drive.h drivemotor.h e2const.h emptyslot.h emulator.h firmwarecard.h font3x5.h gui.h \
|
||||
hypermode.h keyboardbuffermode.h keyboard.h languagecard.h lowpass_1_5_mhz.h \
|
||||
lowpass_3_58_mhz.h lss.h memory.h paddlebuttonstates.h paddles.h picturegenerator.h \
|
||||
|
@ -22,11 +22,12 @@
|
||||
#include "paddles.h"
|
||||
#include "paddlebuttonstates.h"
|
||||
#include "speakerclicker.h"
|
||||
#include "cassette.h"
|
||||
#include "cassettein.h"
|
||||
#include "cassetteout.h"
|
||||
#include "slots.h"
|
||||
|
||||
AddressBus::AddressBus(Memory& ram, Memory& rom, Keyboard& kbd, VideoMode& vid, Paddles& paddles, PaddleButtonStates& paddleButtonStates, SpeakerClicker& speaker, Cassette& cassette, Slots& slts):
|
||||
ram(ram), rom(rom), kbd(kbd), vid(vid), paddles(paddles), paddleButtonStates(paddleButtonStates), speaker(speaker), cassette(cassette), slts(slts)
|
||||
AddressBus::AddressBus(Memory& ram, Memory& rom, Keyboard& kbd, VideoMode& vid, Paddles& paddles, PaddleButtonStates& paddleButtonStates, SpeakerClicker& speaker, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Slots& slts):
|
||||
ram(ram), rom(rom), kbd(kbd), vid(vid), paddles(paddles), paddleButtonStates(paddleButtonStates), speaker(speaker), cassetteIn(cassetteIn), cassetteOut(cassetteOut), slts(slts)
|
||||
{
|
||||
}
|
||||
|
||||
@ -132,7 +133,7 @@ unsigned char AddressBus::readSwitch(unsigned short address)
|
||||
}
|
||||
else if (islot == 0x2)
|
||||
{
|
||||
this->cassette.output();
|
||||
this->cassetteOut.output();
|
||||
}
|
||||
else if (islot == 0x3)
|
||||
{
|
||||
@ -158,7 +159,7 @@ unsigned char AddressBus::readSwitch(unsigned short address)
|
||||
int sw2 = iswch & 0x7;
|
||||
if (sw2 == 0)
|
||||
{
|
||||
setD7(this->cassette.input());
|
||||
setD7(this->cassetteIn.input());
|
||||
}
|
||||
else if (sw2 < 4)
|
||||
{
|
||||
|
@ -24,7 +24,8 @@ class VideoMode;
|
||||
class Paddles;
|
||||
class PaddleButtonStates;
|
||||
class SpeakerClicker;
|
||||
class Cassette;
|
||||
class CassetteIn;
|
||||
class CassetteOut;
|
||||
class Slots;
|
||||
|
||||
class AddressBus
|
||||
@ -37,13 +38,14 @@ private:
|
||||
Paddles& paddles;
|
||||
PaddleButtonStates& paddleButtonStates;
|
||||
SpeakerClicker& speaker;
|
||||
Cassette& cassette;
|
||||
Slots& slts;
|
||||
CassetteIn& cassetteIn;
|
||||
CassetteOut& cassetteOut;
|
||||
Slots& slts;
|
||||
|
||||
unsigned char data; // this emulates the (floating) data bus
|
||||
|
||||
public:
|
||||
AddressBus(Memory& ram, Memory& rom, Keyboard& kbd, VideoMode& vid, Paddles& paddles, PaddleButtonStates& paddleButtonStates, SpeakerClicker& speaker, Cassette& cassette, Slots& slts);
|
||||
AddressBus(Memory& ram, Memory& rom, Keyboard& kbd, VideoMode& vid, Paddles& paddles, PaddleButtonStates& paddleButtonStates, SpeakerClicker& speaker, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Slots& slts);
|
||||
~AddressBus();
|
||||
|
||||
unsigned char read(const unsigned short address);
|
||||
|
@ -43,8 +43,9 @@ Apple2::Apple2(KeypressQueue& keypresses, PaddleButtonStates& paddleButtonStates
|
||||
kbd(keypresses,fhyper,buffered),
|
||||
rom(AddressBus::MOTHERBOARD_ROM_SIZ),
|
||||
ram(AddressBus::MOTHERBOARD_RAM_SIZ),
|
||||
cassette(gui),
|
||||
addressBus(ram,rom,kbd,videoMode,paddles,paddleButtonStates,speaker,cassette,slts),
|
||||
cassetteIn(gui),
|
||||
cassetteOut(gui),
|
||||
addressBus(ram,rom,kbd,videoMode,paddles,paddleButtonStates,speaker,cassetteIn,cassetteOut,slts),
|
||||
picgen(tv,videoMode,this->revision),
|
||||
video(videoMode,addressBus,picgen,textRows),
|
||||
#ifdef USE_EMU
|
||||
@ -69,7 +70,8 @@ void Apple2::tick() {
|
||||
this->video.tick();
|
||||
this->paddles.tick();
|
||||
this->speaker.tick();
|
||||
this->cassette.tick();
|
||||
this->cassetteIn.tick();
|
||||
this->cassetteOut.tick();
|
||||
|
||||
if (this->revision > 0) {
|
||||
this->powerUpReset.tick();
|
||||
|
@ -33,7 +33,8 @@
|
||||
#include "speakerclicker.h"
|
||||
#include "analogtv.h"
|
||||
#include "powerupreset.h"
|
||||
#include "cassette.h"
|
||||
#include "cassettein.h"
|
||||
#include "cassetteout.h"
|
||||
#include "Emu6502.h"
|
||||
#include <fstream>
|
||||
class Emulator;
|
||||
@ -51,8 +52,9 @@ class Apple2 : public Timable
|
||||
SpeakerClicker speaker;
|
||||
Memory rom;
|
||||
Memory ram;
|
||||
Cassette cassette;
|
||||
AddressBus addressBus;
|
||||
CassetteIn cassetteIn;
|
||||
CassetteOut cassetteOut;
|
||||
AddressBus addressBus;
|
||||
PictureGenerator picgen;
|
||||
TextCharacters textRows;
|
||||
Video video;
|
||||
|
255
src/cassette.cpp
255
src/cassette.cpp
@ -1,6 +1,7 @@
|
||||
/*
|
||||
epple2
|
||||
Copyright (C) 2008 by Christopher A. Mosher <cmosher01@gmail.com>
|
||||
|
||||
Copyright © 2008, 2019, Christopher Alan Mosher, Shelton, CT, USA. <cmosher01@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -15,195 +16,97 @@
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
cassette tape image file format
|
||||
-------------------------------
|
||||
Each byte represents one half cycle, in 10-microsoecond units.
|
||||
For example, consider the following values in the file (decimal):
|
||||
|
||||
65 65 65 65 65 20 25 50 50 25 25 25 25 50 50
|
||||
|
||||
This represents the following half-cycles (in microseconds)
|
||||
650 650 650 650 650 200 250 500 500 250 250 250 250 500 500
|
||||
which has the following meaning:
|
||||
|
||||
|--------HEADER-----|--sync-|-1-bit-|-0-bit-|-0-bit-|-1-bit-|
|
||||
| | | | | | |
|
||||
|650 650 650 650 650|200 250|500 500|250 250|250 250|500 500|
|
||||
*/
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include "tinyfiledialogs.h"
|
||||
#include "cassette.h"
|
||||
|
||||
Cassette::Cassette(ScreenImage& gui):
|
||||
gui(gui), t(0), prevT(0), markT(0), positive(false)
|
||||
{
|
||||
unload();
|
||||
gui(gui),
|
||||
t(0),
|
||||
t_active(0),
|
||||
playing(false),
|
||||
modified(false) {
|
||||
}
|
||||
|
||||
Cassette::~Cassette() {
|
||||
}
|
||||
|
||||
void Cassette::note(const char *n) {
|
||||
std::cout << "cassette (" << port() << "): " << n << std::endl;
|
||||
}
|
||||
|
||||
void Cassette::tick() {
|
||||
if (this->playing) {
|
||||
++this->t;
|
||||
|
||||
/*
|
||||
* Automatically stop the tape if the Apple doesn't use
|
||||
* it within the given number of cycles.
|
||||
*/
|
||||
if (this->t_active+100000 <= this->t) {
|
||||
note("STOP");
|
||||
std::cout << "cassette: t=" << this->t << std::endl;
|
||||
this->playing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Cassette::~Cassette()
|
||||
{
|
||||
|
||||
// 0=cancel(abort), 1=yes(save), 2=no(discard)
|
||||
static int askSave() {
|
||||
return tinyfd_messageBox(
|
||||
"Save changes",
|
||||
"You have unsaved changes to your tape image.\nDo you want to SAVE them?",
|
||||
"yesnocancel",
|
||||
"warning",
|
||||
0);
|
||||
}
|
||||
|
||||
void Cassette::tick()
|
||||
{
|
||||
++this->t;
|
||||
// TODO: check for roll-over of tick-count in Cassette???
|
||||
// if (this->t == 0)
|
||||
bool Cassette::eject() {
|
||||
if (isLoaded()) {
|
||||
if (isModified()) {
|
||||
const int resp = askSave();
|
||||
if (resp == 0) { // cancel
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resp == 1) { // yes (save)
|
||||
if (!write()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this->modified = false;
|
||||
this->gui.setCassetteDirty(false);
|
||||
}
|
||||
this->file.clear();
|
||||
this->playing = false;
|
||||
this->t = 0;
|
||||
this->t_active = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Cassette::output()
|
||||
{
|
||||
if (isWriteProtected() || !isLoaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (this->half_cycles.size() <= this->pos)
|
||||
{
|
||||
this->half_cycles.push_back(getHalfCycleTime());
|
||||
this->pos = this->half_cycles.size();
|
||||
this->gui.setCassettePos(this->pos,this->half_cycles.size());
|
||||
this->modified = true;
|
||||
this->gui.setCassetteDirty(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO should we allow overwriting of cassestte tape data?
|
||||
// If so, need to watch out because while reading Apple will
|
||||
// be calling Cassette::output, too.
|
||||
}
|
||||
|
||||
this->prevT = this->t;
|
||||
void Cassette::save() {
|
||||
if (isLoaded()) {
|
||||
if (isModified()) {
|
||||
if (write()) {
|
||||
this->modified = false;
|
||||
this->gui.setCassetteDirty(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char Cassette::getHalfCycleTime() // in 10-microsecond units
|
||||
{
|
||||
const unsigned int delta_cycles(this->t-this->prevT);
|
||||
if (delta_cycles < 225)
|
||||
return 20;
|
||||
if (delta_cycles < 375)
|
||||
return 25;
|
||||
if (delta_cycles < 575)
|
||||
return 50;
|
||||
return 65;
|
||||
|
||||
|
||||
bool Cassette::isLoaded() {
|
||||
return !this->file.empty();
|
||||
}
|
||||
|
||||
bool Cassette::input()
|
||||
{
|
||||
if (!isLoaded())
|
||||
{
|
||||
// no tape loaded
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this->markT <= this->t) // we've hit our mark
|
||||
{
|
||||
this->positive = !this->positive;
|
||||
if (this->pos < this->half_cycles.size())
|
||||
{
|
||||
// set our next mark to be at the end of next half-cycle read from tape
|
||||
this->markT = this->t+this->half_cycles[this->pos++]*10;
|
||||
this->gui.setCassettePos(this->pos,this->half_cycles.size());
|
||||
}
|
||||
}
|
||||
|
||||
return this->positive;
|
||||
}
|
||||
|
||||
void Cassette::rewind()
|
||||
{
|
||||
this->pos = 0;
|
||||
this->gui.setCassettePos(this->pos,this->half_cycles.size());
|
||||
}
|
||||
|
||||
bool Cassette::newFile(const std::string& filePath)
|
||||
{
|
||||
std::ifstream in(filePath.c_str(),std::ios::binary|std::ios::in);
|
||||
if (in.is_open())
|
||||
{
|
||||
in.close();
|
||||
std::cerr << "Error creating file; file already exists: " << filePath << std::endl;
|
||||
return false;
|
||||
}
|
||||
std::ofstream out(filePath.c_str(),std::ios::binary|std::ios::out);
|
||||
out.close();
|
||||
return load(filePath);
|
||||
}
|
||||
|
||||
bool Cassette::load(const std::string& filePath)
|
||||
{
|
||||
// TODO better I/O error handling during cassette loading and saving
|
||||
std::ifstream in(filePath.c_str(),std::ios::binary|std::ios::in|std::ios::ate);
|
||||
if (!in.is_open())
|
||||
{
|
||||
std::cerr << "Error loading " << filePath << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (isLoaded())
|
||||
{
|
||||
unload();
|
||||
}
|
||||
|
||||
const std::ifstream::pos_type size = in.tellg();
|
||||
if (size > 0)
|
||||
{
|
||||
this->half_cycles.resize(size);
|
||||
in.seekg(0,std::ios::beg);
|
||||
in.read((char*)&this->half_cycles[0],size);
|
||||
// std::cerr << "Read " << size << " bytes from " << filePath << std::endl;
|
||||
}
|
||||
in.close();
|
||||
|
||||
this->filePath = filePath;
|
||||
|
||||
checkForWriteProtection();
|
||||
|
||||
this->loaded = true;
|
||||
this->modified = false;
|
||||
|
||||
this->gui.setCassetteFile(filePath);
|
||||
this->gui.setCassetteDirty(false);
|
||||
this->gui.setCassettePos(this->pos,size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Cassette::checkForWriteProtection()
|
||||
{
|
||||
std::ofstream outf(filePath.c_str(),std::ios::binary|std::ios::app);
|
||||
this->writable = outf.is_open();
|
||||
outf.close();
|
||||
}
|
||||
|
||||
void Cassette::save()
|
||||
{
|
||||
if (isWriteProtected() || !isLoaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::ofstream out(filePath.c_str(),std::ios::binary);
|
||||
out.write((char*)&this->half_cycles[0],this->half_cycles.size());
|
||||
out.flush();
|
||||
out.close();
|
||||
|
||||
this->modified = false;
|
||||
this->gui.setCassetteDirty(false);
|
||||
}
|
||||
|
||||
void Cassette::unload()
|
||||
{
|
||||
this->pos = 0;
|
||||
this->writable = true;
|
||||
this->loaded = false;
|
||||
this->filePath = "";
|
||||
this->modified = false;
|
||||
this->half_cycles.clear();
|
||||
this->gui.setCassetteFile("");
|
||||
this->gui.setCassetteDirty(false);
|
||||
this->gui.setCassettePos(this->pos,this->half_cycles.size());
|
||||
bool Cassette::isModified() {
|
||||
return this->modified;
|
||||
}
|
||||
|
@ -1,83 +1,57 @@
|
||||
/*
|
||||
epple2
|
||||
Copyright (C) 2008 by Christopher A. Mosher <cmosher01@gmail.com>
|
||||
epple2
|
||||
Copyright (C) 2008 by Christopher A. Mosher <cmosher01@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CASSETTE_H
|
||||
#define CASSETTE_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
#include "screenimage.h"
|
||||
|
||||
class Cassette
|
||||
{
|
||||
private:
|
||||
ScreenImage& gui;
|
||||
class Cassette {
|
||||
protected:
|
||||
ScreenImage& gui;
|
||||
|
||||
unsigned int t;
|
||||
unsigned int prevT;
|
||||
std::uint_fast32_t t;
|
||||
std::uint_fast32_t t_active;
|
||||
|
||||
unsigned int markT;
|
||||
bool positive;
|
||||
bool playing; // tape is moving
|
||||
|
||||
std::vector<unsigned char> half_cycles;
|
||||
bool modified;
|
||||
std::string file;
|
||||
|
||||
std::string fileName;
|
||||
std::string filePath;
|
||||
bool writable;
|
||||
bool loaded;
|
||||
unsigned int pos;
|
||||
bool modified;
|
||||
// save all data to file, return true if it worked
|
||||
virtual bool write() { return true; }
|
||||
virtual std::string port() { return ""; }
|
||||
void note(const char *n);
|
||||
|
||||
void checkForWriteProtection();
|
||||
public:
|
||||
Cassette(ScreenImage& gui);
|
||||
virtual ~Cassette();
|
||||
|
||||
unsigned char getHalfCycleTime(); // in 10-microsecond units
|
||||
virtual bool eject(); // returns false if user cancels operation
|
||||
void save();
|
||||
|
||||
public:
|
||||
Cassette(ScreenImage& gui);
|
||||
~Cassette();
|
||||
bool isLoaded();
|
||||
bool isModified();
|
||||
|
||||
void tick();
|
||||
void output();
|
||||
bool input();
|
||||
void rewind();
|
||||
bool newFile(const std::string& filePath);
|
||||
bool load(const std::string& filePath);
|
||||
std::string getFileName()
|
||||
{
|
||||
return this->fileName;
|
||||
}
|
||||
|
||||
bool isLoaded()
|
||||
{
|
||||
return this->loaded;
|
||||
}
|
||||
|
||||
void save();
|
||||
void unload();
|
||||
bool isWriteProtected()
|
||||
{
|
||||
return !this->writable;
|
||||
}
|
||||
|
||||
bool isModified()
|
||||
{
|
||||
return this->modified;
|
||||
}
|
||||
virtual void tick();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
259
src/cassettein.cpp
Normal file
259
src/cassettein.cpp
Normal file
@ -0,0 +1,259 @@
|
||||
/*
|
||||
epple2
|
||||
|
||||
Copyright © 2008, 2019, Christopher Alan Mosher, Shelton, CT, USA. <cmosher01@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <SDL2/SDL_audio.h>
|
||||
#include <SDL2/SDL_log.h>
|
||||
#include "tinyfiledialogs.h"
|
||||
#include "cassettein.h"
|
||||
#include "e2const.h"
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
CassetteIn::CassetteIn(ScreenImage& gui):
|
||||
Cassette(gui),
|
||||
samp(nullptr),
|
||||
samp_siz(0),
|
||||
positive(false),
|
||||
slope_was(0) {
|
||||
this->gui.setCassetteInFile("[empty]");
|
||||
}
|
||||
|
||||
CassetteIn::~CassetteIn() {
|
||||
}
|
||||
|
||||
std::string CassetteIn::port() {
|
||||
return "IN";
|
||||
}
|
||||
|
||||
void CassetteIn::note_pos() {
|
||||
std::cout << "cassette: ";
|
||||
std::cout << "t=" << this->t;
|
||||
std::cout << ", ";
|
||||
std::cout << "t_act=" << this->t_active;
|
||||
if (this->playing) {
|
||||
std::cout << "+";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
static const float MOVEMENT_THRESHOLD = 0.001f;
|
||||
|
||||
/* prev --> curr: rising=+1, falling=-1, flat=0 */
|
||||
static std::int_fast8_t slope(const float prev, const float curr) {
|
||||
if (abs(curr-prev) < MOVEMENT_THRESHOLD) {
|
||||
return 0;
|
||||
}
|
||||
return prev < curr ? +1 : -1;
|
||||
}
|
||||
|
||||
//static std::uint_fast32_t prev_p = 0;
|
||||
//static std::uint_fast32_t ccc = 0;
|
||||
|
||||
void CassetteIn::tick() {
|
||||
Cassette::tick();
|
||||
if (this->t % 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->playing) {
|
||||
const std::uint_fast32_t p = this->t/10 > 0 ? this->t/10 : 1;
|
||||
|
||||
if (this->samp_siz <= p) {
|
||||
note("END OF TAPE");
|
||||
this->t = this->samp_siz*10;
|
||||
this->t_active = this->t;
|
||||
this->playing = false;
|
||||
this->gui.setCassettePos(this->samp_siz,this->samp_siz);
|
||||
note_pos();
|
||||
}
|
||||
|
||||
this->gui.setCassettePos(p,this->samp_siz);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Reconstruct original square wave signal (from the Apple cassette output port)
|
||||
* from the cassette tape's (lagging) sine wave. Assume the cassette's recorded voltage level
|
||||
* lagged from its input voltage. Therefore, here we check for the sine wave BEGINNING
|
||||
* to react to what we assume is the input voltage's (square wave) swing, and we use
|
||||
* that as the signal to send up to the emulator.
|
||||
*
|
||||
* ---+ +-----
|
||||
* .\ /
|
||||
* . \ /
|
||||
* . \ /
|
||||
* . +-----+
|
||||
* . .
|
||||
* . .
|
||||
* ---+ +---------
|
||||
* | |
|
||||
* | |
|
||||
* | |
|
||||
* +---------+
|
||||
*/
|
||||
std::int_fast8_t slope_is = slope(this->samp[p-1], this->samp[p]);
|
||||
if (slope_is != 0 && slope_is != this->slope_was) {
|
||||
this->positive = !this->positive;
|
||||
// std::printf("%d ", (p-prev_p));
|
||||
// if (!(++ccc % 40)) {
|
||||
// std::printf("\n");
|
||||
// }
|
||||
// prev_p = p;
|
||||
this->slope_was = slope_is;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CassetteIn::input() {
|
||||
if (!isLoaded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this->playing) {
|
||||
if (this->t/10 < this->samp_siz) {
|
||||
note("PLAY");
|
||||
this->playing = true;
|
||||
note_pos();
|
||||
}
|
||||
}
|
||||
|
||||
if (this->playing) {
|
||||
this->t_active = this->t;
|
||||
}
|
||||
|
||||
return this->positive;
|
||||
}
|
||||
|
||||
void CassetteIn::rewind() {
|
||||
if (!isLoaded()) {
|
||||
return;
|
||||
}
|
||||
note_pos();
|
||||
|
||||
note("REWIND");
|
||||
this->t = 0;
|
||||
this->t_active = 0;
|
||||
this->slope_was = 0;
|
||||
this->positive = false;
|
||||
this->playing = false;
|
||||
note_pos();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
note("FAST FORWARD TO TONE");
|
||||
|
||||
const unsigned int HEAD_SAMPLES = 17;
|
||||
std::int_fast8_t slope_was = 0;
|
||||
uint i_was = 0;
|
||||
uint c_head = 0;
|
||||
for (std::uint_fast32_t i = 1; i < this->samp_siz; ++i) {
|
||||
std::int_fast8_t slope_is = slope(this->samp[i-1], this->samp[i]);
|
||||
if (slope_is) {
|
||||
if (slope_is != slope_was) {
|
||||
// std::printf("%d ", (i-prev_p));
|
||||
// if (!(++ccc % 40)) {
|
||||
// std::printf("\n");
|
||||
// }
|
||||
// prev_p = i;
|
||||
this->positive = !this->positive;
|
||||
this->slope_was = slope_is;
|
||||
const std::uint_fast32_t d = i-i_was;
|
||||
if (59u <= d && d <= 71u) {
|
||||
++c_head;
|
||||
// std::printf("=====[%d]====", c_head);
|
||||
// fast-forward to the header tone
|
||||
if (HEAD_SAMPLES <= c_head) {
|
||||
// prev_p = i;
|
||||
this->t = i*10-1;
|
||||
this->t_active = this->t;
|
||||
// ccc = 0;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
c_head = 0;
|
||||
}
|
||||
i_was = i;
|
||||
slope_was = slope_is;
|
||||
}
|
||||
}
|
||||
}
|
||||
note_pos();
|
||||
this->gui.setCassettePos(this->t/10,this->samp_siz);
|
||||
}
|
||||
|
||||
bool CassetteIn::load(const std::string& filePath) {
|
||||
SDL_AudioSpec wav_spec;
|
||||
std::uint8_t *wav_buffer;
|
||||
std::uint32_t wav_length;
|
||||
|
||||
if (SDL_LoadWAV(filePath.c_str(), &wav_spec, &wav_buffer, &wav_length) == nullptr) {
|
||||
SDL_Log("Error: %s ; file: %s\n", SDL_GetError(), filePath.c_str());
|
||||
return false;
|
||||
}
|
||||
// std::printf("opened WAVE file %s\n", filePath.c_str());
|
||||
// std::printf(" buffer size: %d bytes\n", wav_length);
|
||||
// std::printf(" sample rate: %dHz\n", wav_spec.freq);
|
||||
// std::printf(" sample datatype: %04X\n", wav_spec.format);
|
||||
// std::printf(" channels: %d\n", wav_spec.channels);
|
||||
// std::printf(" silence value: %d\n", wav_spec.silence);
|
||||
// std::printf(" sample count: %d\n", wav_spec.samples);
|
||||
|
||||
if (isLoaded()) {
|
||||
if (!eject()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* convert input sample to floating-point, at rate of 10 CPU cycles per sample, for easy calculation */
|
||||
SDL_AudioCVT cvt;
|
||||
SDL_BuildAudioCVT(&cvt, wav_spec.format, wav_spec.channels, wav_spec.freq, AUDIO_F32, 1, E2Const::AVG_CPU_HZ/10);
|
||||
cvt.len = wav_length;
|
||||
cvt.buf = reinterpret_cast<std::uint8_t*>(std::malloc(cvt.len_mult * cvt.len));
|
||||
memcpy(cvt.buf, wav_buffer, cvt.len);
|
||||
SDL_FreeWAV(wav_buffer);
|
||||
|
||||
SDL_ConvertAudio(&cvt);
|
||||
this->samp = reinterpret_cast<float*>(cvt.buf);
|
||||
this->samp_siz = cvt.len_cvt/4;
|
||||
|
||||
note("LOAD");
|
||||
note_pos();
|
||||
|
||||
this->file = filePath;
|
||||
this->gui.setCassetteInFile(filePath);
|
||||
|
||||
rewind();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CassetteIn::eject() {
|
||||
const bool ok = Cassette::eject();
|
||||
if (ok) {
|
||||
this->gui.setCassetteInFile("(no tape)");
|
||||
this->gui.setCassettePos(0,0);
|
||||
std::free(this->samp);
|
||||
this->samp_siz = 0;
|
||||
}
|
||||
return ok;
|
||||
}
|
48
src/cassettein.h
Normal file
48
src/cassettein.h
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
epple2
|
||||
Copyright (C) 2008 by Christopher A. Mosher <cmosher01@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CASSETTEIN_H
|
||||
#define CASSETTEIN_H
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include "cassette.h"
|
||||
|
||||
class CassetteIn : public Cassette {
|
||||
private:
|
||||
float *samp;
|
||||
std::uint32_t samp_siz;
|
||||
|
||||
bool positive;
|
||||
std::int_fast8_t slope_was;
|
||||
|
||||
virtual std::string port();
|
||||
void note_pos();
|
||||
|
||||
public:
|
||||
CassetteIn(ScreenImage& gui);
|
||||
virtual ~CassetteIn();
|
||||
|
||||
virtual void tick();
|
||||
bool input();
|
||||
|
||||
bool load(const std::string& filePath);
|
||||
void rewind();
|
||||
virtual bool eject();
|
||||
};
|
||||
|
||||
#endif
|
178
src/cassetteout.cpp
Normal file
178
src/cassetteout.cpp
Normal file
@ -0,0 +1,178 @@
|
||||
/*
|
||||
epple2
|
||||
|
||||
Copyright © 2008, 2019, Christopher Alan Mosher, Shelton, CT, USA. <cmosher01@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <SDL2/SDL_audio.h>
|
||||
#include <SDL2/SDL_log.h>
|
||||
#include "tinyfiledialogs.h"
|
||||
#include "cassetteout.h"
|
||||
#include "e2const.h"
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
CassetteOut::CassetteOut(ScreenImage& gui):
|
||||
Cassette(gui) {
|
||||
this->gui.setCassetteOutFile("[empty]");
|
||||
}
|
||||
|
||||
|
||||
CassetteOut::~CassetteOut() {
|
||||
}
|
||||
|
||||
std::string CassetteOut::port() {
|
||||
return "OUT";
|
||||
}
|
||||
|
||||
//void CassetteOut::note_pos() {
|
||||
// std::cout << "cassette: ";
|
||||
// std::cout << "t=" << this->t;
|
||||
// std::cout << ", ";
|
||||
// std::cout << "t(in )=" << this->playing_t;
|
||||
// if (this->playing) {
|
||||
// std::cout << "*";
|
||||
// }
|
||||
// std::cout << ", ";
|
||||
// std::cout << "t(out)=" << this->recording_t;
|
||||
// if (this->recording) {
|
||||
// std::cout << "*";
|
||||
// }
|
||||
// if (this->samp_out.size()) {
|
||||
// std::cout << "[" << this->samp_out.size() << "]";
|
||||
// }
|
||||
// std::cout << std::endl;
|
||||
//}
|
||||
|
||||
void CassetteOut::tick() {
|
||||
Cassette::tick();
|
||||
}
|
||||
|
||||
void CassetteOut::output() {
|
||||
if (isLoaded()) {
|
||||
if (!this->playing) {
|
||||
this->playing = true;
|
||||
note("PLAY+RECORD");
|
||||
// note_pos();
|
||||
} else {
|
||||
const std::uint32_t d = this->t-this->t_active;
|
||||
this->samp_out.push_back(d);
|
||||
this->modified = true;
|
||||
this->gui.setCassetteDirty(true);
|
||||
}
|
||||
this->t_active = this->t;
|
||||
}
|
||||
}
|
||||
|
||||
bool CassetteOut::blank(const std::string& filePath) {
|
||||
if (!eject()) {
|
||||
return false;
|
||||
}
|
||||
std::ifstream in(filePath.c_str(),std::ios::binary|std::ios::in);
|
||||
if (in.is_open()) {
|
||||
in.close();
|
||||
std::cerr << "Error creating file; file already exists: " << filePath << std::endl;
|
||||
return false;
|
||||
}
|
||||
std::ofstream out(filePath.c_str(),std::ios::binary|std::ios::out);
|
||||
out.close();
|
||||
if (out) {
|
||||
this->file = filePath;
|
||||
this->gui.setCassetteOutFile(this->file);
|
||||
this->samp_out.clear();
|
||||
} else {
|
||||
std::cerr << "Error creating file: " << filePath << std::endl;
|
||||
}
|
||||
return (bool)out;
|
||||
}
|
||||
|
||||
bool CassetteOut::eject() {
|
||||
const bool ok = Cassette::eject();
|
||||
if (ok) {
|
||||
this->gui.setCassetteOutFile("(no tape)");
|
||||
this->samp_out.clear();
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool CassetteOut::write() {
|
||||
std::ofstream out(this->file.c_str(), std::ios::binary);
|
||||
|
||||
|
||||
|
||||
std::uint32_t c_sample = 0;
|
||||
for (std::uint32_t i = 0; i < this->samp_out.size(); ++i) {
|
||||
if (this->samp_out[i] < 3000) {
|
||||
c_sample += this->samp_out[i]/50;
|
||||
}
|
||||
}
|
||||
|
||||
std::uint32_t longVal;
|
||||
std::uint16_t wordVal;
|
||||
|
||||
out.write("RIFF", 4);
|
||||
out.write((char*)&(longVal = 36+c_sample), 4);
|
||||
out.write("WAVE", 4);
|
||||
|
||||
|
||||
|
||||
out.write("fmt ", 4);
|
||||
out.write((char*)&(longVal = 16), 4);
|
||||
|
||||
out.write((char*)&(wordVal = 1), 2); // PCM
|
||||
out.write((char*)&(wordVal = 1), 2); // mono, one channel
|
||||
|
||||
out.write((char*)&(longVal = E2Const::AVG_CPU_HZ/50), 4); // samples per second
|
||||
out.write((char*)&(longVal = E2Const::AVG_CPU_HZ/50), 4); // byte rate (same)
|
||||
out.write((char*)&(wordVal = 1), 2); // alignment
|
||||
out.write((char*)&(wordVal = 8), 2); // bits per sample
|
||||
|
||||
|
||||
|
||||
out.write("data", 4);
|
||||
out.write((char*)&(longVal = c_sample), 4);
|
||||
|
||||
const float pi = acos(-1);
|
||||
bool positive = false;
|
||||
for (std::uint32_t i = 0; i < this->samp_out.size(); ++i) {
|
||||
if (this->samp_out[i] < 3000) {
|
||||
for (std::uint_fast8_t s = 0; s < this->samp_out[i]/50; ++s) {
|
||||
float x = sin(pi/2 + 50*pi*s/this->samp_out[i]);
|
||||
if (!positive) {
|
||||
x = -1.0f*x;
|
||||
}
|
||||
x = round(128+128*x);
|
||||
if (x > 255.0f) {
|
||||
x = 255.0f;
|
||||
}
|
||||
if (x < 0.0f) {
|
||||
x = 0.0f;
|
||||
}
|
||||
std::uint8_t bx = x;
|
||||
out.write((char*)&bx, 1);
|
||||
}
|
||||
}
|
||||
positive = !positive;
|
||||
}
|
||||
|
||||
|
||||
|
||||
out.flush();
|
||||
out.close();
|
||||
return (bool)out;
|
||||
}
|
44
src/cassetteout.h
Normal file
44
src/cassetteout.h
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
epple2
|
||||
Copyright (C) 2008 by Christopher A. Mosher <cmosher01@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CASSETTEOUT_H
|
||||
#define CASSETTEOUT_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include "cassette.h"
|
||||
|
||||
class CassetteOut : public Cassette {
|
||||
private:
|
||||
std::vector<std::uint32_t> samp_out;
|
||||
|
||||
virtual std::string port();
|
||||
|
||||
public:
|
||||
CassetteOut(ScreenImage& gui);
|
||||
virtual ~CassetteOut();
|
||||
|
||||
virtual void tick();
|
||||
void output();
|
||||
|
||||
bool blank(const std::string& filePath);
|
||||
virtual bool write();
|
||||
virtual bool eject();
|
||||
};
|
||||
|
||||
#endif
|
@ -25,7 +25,8 @@
|
||||
#include "standardout.h"
|
||||
#include "standardin.h"
|
||||
#include "clockcard.h"
|
||||
#include "cassette.h"
|
||||
#include "cassettein.h"
|
||||
#include "cassetteout.h"
|
||||
#include "tinyfiledialogs.h"
|
||||
|
||||
#include <iostream>
|
||||
@ -74,7 +75,7 @@ static void trim(std::string& str)
|
||||
}
|
||||
}
|
||||
|
||||
void Config::parse(Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, Cassette& cassette)
|
||||
void Config::parse(Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut)
|
||||
{
|
||||
std::ifstream* pConfig;
|
||||
|
||||
@ -144,7 +145,7 @@ void Config::parse(Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenI
|
||||
trim(line);
|
||||
if (!line.empty())
|
||||
{
|
||||
parseLine(line,ram,rom,slts,revision,gui,cassette);
|
||||
parseLine(line,ram,rom,slts,revision,gui,cassetteIn,cassetteOut);
|
||||
}
|
||||
std::getline(*pConfig,line);
|
||||
}
|
||||
@ -153,11 +154,11 @@ void Config::parse(Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenI
|
||||
|
||||
// TODO: make sure there is no more than ONE stdin and/or ONE stdout card
|
||||
}
|
||||
void Config::parseLine(const std::string& line, Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, Cassette& cassette)
|
||||
void Config::parseLine(const std::string& line, Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut)
|
||||
{
|
||||
try
|
||||
{
|
||||
tryParseLine(line,ram,rom,slts,revision,gui,cassette);
|
||||
tryParseLine(line,ram,rom,slts,revision,gui,cassetteIn,cassetteOut);
|
||||
}
|
||||
catch (const ConfigException& err)
|
||||
{
|
||||
@ -165,7 +166,7 @@ void Config::parseLine(const std::string& line, Memory& ram, Memory& rom, Slots&
|
||||
}
|
||||
}
|
||||
|
||||
void Config::tryParseLine(const std::string& line, Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, Cassette& cassette)
|
||||
void Config::tryParseLine(const std::string& line, Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut)
|
||||
{
|
||||
std::istringstream tok(line);
|
||||
|
||||
@ -301,29 +302,49 @@ void Config::tryParseLine(const std::string& line, Memory& ram, Memory& rom, Slo
|
||||
|
||||
if (cas == "rewind")
|
||||
{
|
||||
cassette.rewind();
|
||||
cassetteIn.rewind();
|
||||
}
|
||||
else if (cas == "new")
|
||||
else if (cas == "blank")
|
||||
{
|
||||
std::string fcas;
|
||||
std::getline(tok,fcas);
|
||||
trim(fcas);
|
||||
cassette.newFile(fcas);
|
||||
if (!fcas.empty()) {
|
||||
cassetteOut.blank(fcas);
|
||||
}
|
||||
}
|
||||
else if (cas == "load")
|
||||
{
|
||||
std::string fcas;
|
||||
std::getline(tok,fcas);
|
||||
trim(fcas);
|
||||
cassette.load(fcas);
|
||||
std::string fn_optional;
|
||||
std::getline(tok,fn_optional);
|
||||
trim(fn_optional);
|
||||
if (fn_optional.length() == 0) {
|
||||
gui.exitFullScreen();
|
||||
char const *ft[1] = { "*.wav" };
|
||||
char const *fn = tinyfd_openFileDialog("Load cassette audio", "", 1, ft, "WAVE cassette images", 0);
|
||||
if (fn) {
|
||||
fn_optional = std::string(fn);
|
||||
}
|
||||
}
|
||||
if (fn_optional.length() > 0) {
|
||||
cassetteIn.load(fn_optional);
|
||||
}
|
||||
}
|
||||
else if (cas == "unload")
|
||||
else if (cas == "eject")
|
||||
{
|
||||
cassette.unload();
|
||||
}
|
||||
std::string eject;
|
||||
tok >> eject;
|
||||
if (eject == "in") {
|
||||
cassetteIn.eject();
|
||||
} else if (eject == "out") {
|
||||
cassetteOut.eject();
|
||||
} else {
|
||||
throw ConfigException("error: unknown cassette to eject: "+eject);
|
||||
}
|
||||
}
|
||||
else if (cas == "save")
|
||||
{
|
||||
cassette.save();
|
||||
cassetteOut.save();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -22,7 +22,8 @@
|
||||
class Memory;
|
||||
class Slots;
|
||||
class ScreenImage;
|
||||
class Cassette;
|
||||
class CassetteIn;
|
||||
class CassetteOut;
|
||||
|
||||
class ConfigException
|
||||
{
|
||||
@ -41,14 +42,14 @@ private:
|
||||
static void unloadDisk(Slots& slts, int slot, int drive);
|
||||
static void saveDisk(Slots& slts, int slot, int drive);
|
||||
static void insertCard(const std::string& cardType, int slot, Slots& slts, ScreenImage& gui);
|
||||
static void tryParseLine(const std::string& line, Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, Cassette& cassette);
|
||||
static void tryParseLine(const std::string& line, Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut);
|
||||
|
||||
public:
|
||||
Config(const std::string& file_path);
|
||||
~Config();
|
||||
|
||||
void parse(Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, Cassette& cassette);
|
||||
static void parseLine(const std::string& line, Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, Cassette& cassette);
|
||||
void parse(Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut);
|
||||
static void parseLine(const std::string& line, Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -26,7 +26,7 @@ public:
|
||||
EmptySlot() {}
|
||||
virtual ~EmptySlot() {}
|
||||
|
||||
virtual std::string getName() { return "empty"; }
|
||||
virtual std::string getName() { return "[empty]"; }
|
||||
|
||||
// empty slots have no ROMs, so just return data (for floating bus emulation)
|
||||
virtual unsigned char readRom(const unsigned short address, const unsigned char data) { return data; }
|
||||
|
@ -67,7 +67,7 @@ void Emulator::powerOffComputer() {
|
||||
}
|
||||
|
||||
void Emulator::config(Config& cfg) {
|
||||
cfg.parse(this->apple2.ram, this->apple2.rom, this->apple2.slts, this->apple2.revision, this->screenImage, this->apple2.cassette);
|
||||
cfg.parse(this->apple2.ram, this->apple2.rom, this->apple2.slts, this->apple2.revision, this->screenImage, this->apple2.cassetteIn, this->apple2.cassetteOut);
|
||||
}
|
||||
|
||||
void Emulator::init() {
|
||||
@ -432,13 +432,14 @@ void Emulator::cmdKey(const SDL_KeyboardEvent& keyEvent) {
|
||||
|
||||
void Emulator::processCommand() {
|
||||
this->screenImage.exitCommandMode();
|
||||
this->screenImage.drawPower(this->timable == &this->apple2);
|
||||
this->pendingCommandExit = true;
|
||||
|
||||
if (cmdline.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Config::parseLine(cmdline, this->apple2.ram, this->apple2.rom, this->apple2.slts, this->apple2.revision, this->screenImage, this->apple2.cassette);
|
||||
Config::parseLine(cmdline, this->apple2.ram, this->apple2.rom, this->apple2.slts, this->apple2.revision, this->screenImage, this->apple2.cassetteIn, this->apple2.cassetteOut);
|
||||
cmdline.erase(cmdline.begin(), cmdline.end());
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,7 @@ static const char* power =
|
||||
#define SCRW 640
|
||||
#define SCRH 480
|
||||
#define ASPECT_RATIO 1.191 /* UA2, p. 8-28 */
|
||||
#define R_SLOT 67
|
||||
|
||||
static const int HEIGHT = E2Const::VISIBLE_ROWS_PER_FIELD * 2;
|
||||
static const int WIDTH = AppleNTSC::H - AppleNTSC::PIC_START - 2;
|
||||
@ -59,7 +60,8 @@ buffer(true),
|
||||
fillLines(true),
|
||||
display(AnalogTV::MONITOR_COLOR),
|
||||
slotnames(8),
|
||||
cassettename(32, ' ') {
|
||||
cassInName(32, ' '),
|
||||
cassOutName(32, ' ') {
|
||||
createScreen();
|
||||
}
|
||||
|
||||
@ -111,7 +113,7 @@ void ScreenImage::drawLabels() {
|
||||
}
|
||||
|
||||
void ScreenImage::drawSlots() {
|
||||
int r(65);
|
||||
int r(R_SLOT-1);
|
||||
int c(17);
|
||||
drawText("SLOTS:", r++, c);
|
||||
for (int slot(0); slot < 8; ++slot) {
|
||||
@ -132,11 +134,20 @@ void ScreenImage::drawSlot(int slot, int r, int c) {
|
||||
|
||||
void ScreenImage::drawCassette() {
|
||||
int r(65);
|
||||
int c(85);
|
||||
drawText("CASSETTE:", r, c);
|
||||
c += 9;
|
||||
drawText(this->cassettename, r, c);
|
||||
const int len = this->cassettename.length();
|
||||
int c(80);
|
||||
drawText("CASSETTE: IN<-", r, c);
|
||||
c += 15;
|
||||
drawText(this->cassInName, r, c);
|
||||
int len = this->cassInName.length();
|
||||
if (len < 40) {
|
||||
drawText(std::string(40 - len, ' '), r, c + len);
|
||||
}
|
||||
++r;
|
||||
c = 81+9;
|
||||
drawText("OUT->", r, c);
|
||||
c += 5;
|
||||
drawText(this->cassOutName, r, c);
|
||||
len = this->cassOutName.length();
|
||||
if (len < 40) {
|
||||
drawText(std::string(40 - len, ' '), r, c + len);
|
||||
}
|
||||
@ -156,7 +167,7 @@ void ScreenImage::drawFnKeys() {
|
||||
drawText(
|
||||
" XXXXXXXXXXXXXX WINDOW FILL-LINES CMD RESET PASTE SAVE BMP QUIT! REPT HYPER BUFFER ", r++, c);
|
||||
drawText(
|
||||
" F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 ", r++, c);
|
||||
" F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 ", r++, c);
|
||||
|
||||
if (this->fullscreen)
|
||||
invertText(76, 32, 42); // FULLSCRN
|
||||
@ -251,7 +262,7 @@ void ScreenImage::displayHz(int hz) {
|
||||
|
||||
void ScreenImage::drawPower(bool on) {
|
||||
unsigned int* pn = this->pixels;
|
||||
pn += (HEIGHT + 5)*SCRW + 5;
|
||||
pn += (HEIGHT + 35)*SCRW + 5;
|
||||
for (int r = 0; r < POWERD/ASPECT_RATIO; ++r) {
|
||||
if (r < LABEL_Y || LABEL_Y + 7 <= r) {
|
||||
for (int c = 0; c < POWERD; ++c) {
|
||||
@ -337,7 +348,7 @@ void ScreenImage::backspaceCommand() {
|
||||
}
|
||||
|
||||
void ScreenImage::updateSlotName(const int slot, Card* card) {
|
||||
int r(66 + slot);
|
||||
int r(R_SLOT + slot);
|
||||
int c(20);
|
||||
const std::string& name = card->getName();
|
||||
this->slotnames[slot] = name;
|
||||
@ -356,7 +367,7 @@ void ScreenImage::removeCard(const int slot, Card* card /* empty */) {
|
||||
*/
|
||||
void ScreenImage::setDiskFile(int slot, int drive, const std::string& filepath) {
|
||||
std::string f = truncateFilePath(filepath);
|
||||
int r(66 + slot);
|
||||
int r(R_SLOT + slot);
|
||||
int c(37 + 32 * drive);
|
||||
drawText(f, r, c);
|
||||
|
||||
@ -383,7 +394,7 @@ std::string ScreenImage::truncateFilePath(const std::string& filepath) {
|
||||
}
|
||||
|
||||
void ScreenImage::clearCurrentDrive(int slot, int drive) {
|
||||
int r(66 + slot);
|
||||
int r(R_SLOT + slot);
|
||||
int c(35 + 32 * drive);
|
||||
drawChar(' ', r, c);
|
||||
this->slotnames[slot][c - 20] = ' ';
|
||||
@ -393,7 +404,7 @@ void ScreenImage::clearCurrentDrive(int slot, int drive) {
|
||||
}
|
||||
|
||||
void ScreenImage::setCurrentDrive(int slot, int drive, int track, bool on) {
|
||||
int r(66 + slot);
|
||||
int r(R_SLOT + slot);
|
||||
int c(35 + 32 * drive);
|
||||
drawChar(' ', r, c, 0xFFFFFF, on ? 0xFF0000 : 0);
|
||||
c += 15;
|
||||
@ -413,7 +424,7 @@ void ScreenImage::setCurrentDrive(int slot, int drive, int track, bool on) {
|
||||
}
|
||||
|
||||
void ScreenImage::setTrack(int slot, int drive, int track) {
|
||||
int r(66 + slot);
|
||||
int r(R_SLOT + slot);
|
||||
int c(52 + 32 * drive);
|
||||
char nibh = Util::hexDigit((((unsigned char) track) >> 4) & 0xF);
|
||||
drawChar(nibh, r, c);
|
||||
@ -425,19 +436,19 @@ void ScreenImage::setTrack(int slot, int drive, int track) {
|
||||
}
|
||||
|
||||
void ScreenImage::setIO(int slot, int drive, bool on) {
|
||||
int r(66 + slot);
|
||||
int r(R_SLOT + slot);
|
||||
int c(35 + 32 * drive);
|
||||
drawChar(' ', r, c, 0xFFFFFF, on ? 0xFF0000 : 0);
|
||||
}
|
||||
|
||||
void ScreenImage::setDirty(int slot, int drive, bool dirty) {
|
||||
int r(66 + slot);
|
||||
int r(R_SLOT + slot);
|
||||
int c(36 + 32 * drive);
|
||||
drawChar(dirty ? '*' : ' ', r, c);
|
||||
this->slotnames[slot][c - 20] = dirty ? '*' : ' ';
|
||||
}
|
||||
|
||||
void ScreenImage::setCassetteFile(const std::string& filepath) {
|
||||
void ScreenImage::setCassetteInFile(const std::string& filepath) {
|
||||
std::string f = truncateFilePath(filepath);
|
||||
int r(65);
|
||||
int c(85 + 11);
|
||||
@ -449,18 +460,34 @@ void ScreenImage::setCassetteFile(const std::string& filepath) {
|
||||
drawText(d, r, c + f.length());
|
||||
}
|
||||
|
||||
this->cassettename.replace(c - 94, 12, 12, ' ');
|
||||
this->cassettename.replace(c - 94, f.length(), f);
|
||||
this->cassInName.replace(c - 94, 12, 12, ' ');
|
||||
this->cassInName.replace(c - 94, f.length(), f);
|
||||
}
|
||||
|
||||
void ScreenImage::setCassetteOutFile(const std::string& filepath) {
|
||||
std::string f = truncateFilePath(filepath);
|
||||
int r(66);
|
||||
int c(85 + 11);
|
||||
drawText(f, r, c);
|
||||
|
||||
const int dlen = 12 - f.length();
|
||||
if (dlen > 0) {
|
||||
std::string d(dlen, ' ');
|
||||
drawText(d, r, c + f.length());
|
||||
}
|
||||
|
||||
this->cassOutName.replace(c - 94, 12, 12, ' ');
|
||||
this->cassOutName.replace(c - 94, f.length(), f);
|
||||
}
|
||||
|
||||
void ScreenImage::setCassetteDirty(bool dirty) {
|
||||
int r(65);
|
||||
int r(66);
|
||||
int c(85 + 10);
|
||||
drawChar(dirty ? '*' : ' ', r, c);
|
||||
this->cassettename[c - 94] = dirty ? '*' : ' ';
|
||||
this->cassOutName[c - 94] = dirty ? '*' : ' ';
|
||||
}
|
||||
|
||||
void ScreenImage::setCassettePos(int pos, int siz) {
|
||||
void ScreenImage::setCassettePos(unsigned int pos, unsigned int siz) {
|
||||
int r(65);
|
||||
int c(110);
|
||||
std::ostringstream os;
|
||||
@ -474,7 +501,7 @@ void ScreenImage::setCassettePos(int pos, int siz) {
|
||||
0: language RW B2
|
||||
*/
|
||||
void ScreenImage::setLangCard(int slot, bool readEnable, bool writeEnable, int bank) {
|
||||
int r(66 + slot);
|
||||
int r(R_SLOT + slot);
|
||||
int c(29);
|
||||
drawChar(readEnable ? 'R' : ' ', r, c);
|
||||
this->slotnames[slot][c - 20] = readEnable ? 'R' : ' ';
|
||||
@ -496,7 +523,7 @@ void ScreenImage::setLangCard(int slot, bool readEnable, bool writeEnable, int b
|
||||
0: firmware D F8
|
||||
*/
|
||||
void ScreenImage::setFirmCard(int slot, bool bank, bool F8) {
|
||||
int r(66 + slot);
|
||||
int r(R_SLOT + slot);
|
||||
int c(29);
|
||||
drawChar(bank ? 'D' : ' ', r, c);
|
||||
this->slotnames[slot][c - 20] = bank ? 'D' : ' ';
|
||||
|
@ -42,7 +42,8 @@ private:
|
||||
unsigned int cmdpos;
|
||||
void createScreen();
|
||||
std::vector<std::string> slotnames;
|
||||
std::string cassettename;
|
||||
std::string cassInName;
|
||||
std::string cassOutName;
|
||||
|
||||
static std::string truncateFilePath(const std::string& filepath);
|
||||
|
||||
@ -87,9 +88,10 @@ public:
|
||||
void setIO(int slot, int drive, bool on);
|
||||
void setDirty(int slot, int drive, bool dirty);
|
||||
|
||||
void setCassetteFile(const std::string& filepath);
|
||||
void setCassetteDirty(bool dirty);
|
||||
void setCassettePos(int pos, int siz);
|
||||
void setCassetteInFile(const std::string& filepath);
|
||||
void setCassetteOutFile(const std::string& filepath);
|
||||
void setCassetteDirty(bool dirty); // cassette out only
|
||||
void setCassettePos(unsigned int pos, unsigned int siz); // cassette in only
|
||||
|
||||
void setLangCard(int slot, bool readEnable, bool writeEnable, int bank);
|
||||
void setFirmCard(int slot, bool bank, bool F8);
|
||||
|
Loading…
x
Reference in New Issue
Block a user