mirror of
https://github.com/cmosher01/Epple-II.git
synced 2025-03-11 20:30:26 +00:00
cassette docs, save on exit, refactor
This commit is contained in:
parent
c787dbb80c
commit
0e29c34f79
@ -2,52 +2,76 @@ anchor:cassette_tape[]
|
||||
|
||||
=== Cassette Tape Interface
|
||||
|
||||
The Apple ][ and Apple ][ plus machines had the ability to save and load binary
|
||||
The Apple ][ and Apple ][ plus machines have the ability to save and load binary
|
||||
data to and from cassette tape. The user would attach a standard cassette tape
|
||||
recorder to the jacks on the back of the Apple ][, and use the monitor +R+ and +W+
|
||||
commands, or the Applesoft BASIC commands +LOAD+ and +SAVE+, to read and write data
|
||||
recorder to the jacks on the back of the Apple ][, and use the monitor commands
|
||||
+R+ and +W+, or the BASIC commands +LOAD+ and +SAVE+, to read and write data
|
||||
on the cassette tape. The user would have to press the play and/or record buttons
|
||||
on the player at the right time.
|
||||
|
||||
The Epple ][ emulates the cassette interface, using a file to hold the
|
||||
recorded portion of the tape. The file will grow in length as necessary
|
||||
to hold data that the emulated Apple is writing to the ``tape.''
|
||||
The emulator will not overwrite existing data on a tape image.
|
||||
The emulator will automatically ``press'' the play or record buttons that
|
||||
would have been necessary when using the original machine.
|
||||
The Apple ][ has two cassette ports, CASSETTE IN and CASSETTE OUT. To save a program to
|
||||
tape, the user would attach a cassette recorder to the CASSETTE OUT port, load a blank
|
||||
cassette into the recorder, press RECORD (and PLAY), then on the Apple type SAVE. When
|
||||
finished, the user would press STOP, and eject the tape.
|
||||
|
||||
To load a previously saved program
|
||||
from tape, the user would attach the player to the CASSETTE IN port, then load and REWIND
|
||||
the tape. The user would PLAY the tape until the header tone could be heard, then STOP.
|
||||
On the Apple ][ the user would type LOAD and immediately press PLAY on the cassette player.
|
||||
After the file loaded, the user would STOP and eject the tape.
|
||||
|
||||
The Epple ][ emulates the cassette interface, using a standard WAVE (PCM) file to
|
||||
hold the recorded portion of the tape. It provides two ports, one for CASSETTE IN and
|
||||
a separate one for CASSETTE OUT. Generally you'll use only one at a time. Use CASSETTE IN
|
||||
for LOADing a program, or CASSETTE OUT for SAVEing a program.
|
||||
|
||||
To load a program from a cassette image WAVE file, use +cassette load+
|
||||
to put the tape into the cassette player. The tape will automatically rewind and
|
||||
advance to the header tone. Then use the Apple LOAD command to load the program
|
||||
from the tape. If the Apple gives you +ERR+, that means it could not interpret the
|
||||
WAVE audio correctly.
|
||||
If you want to rewind the tape, you can use the +casssette rewind+ command.
|
||||
Use +cassette eject in+ to close the file.
|
||||
|
||||
To save an in-memory program to a cassette tape image WAVE file, use
|
||||
+cassette blank <file-path>+ to put a new blank tape image into
|
||||
the cassette recorder. Then use the Apple SAVE command to record to the tape, and then
|
||||
+cassette save+ to save the WAVE file. Use +cassette eject out+ to close the file.
|
||||
|
||||
The emulator will not overwrite existing data in a tape image file.
|
||||
|
||||
==== Commands
|
||||
|
||||
+cassette new <file-path>+
|
||||
+cassette load [ <file-path> ]+
|
||||
|
||||
This creates a new empty file (on the host computer) that represents a cassette tape image.
|
||||
The file must not already exist.
|
||||
|
||||
+cassette load <file-path>+
|
||||
|
||||
This loads an existing file (from the host computer) containing a cassette tape image.
|
||||
The tape is automatically positioned at its beginning (fully rewound).
|
||||
|
||||
+cassette unload+
|
||||
|
||||
This removes the file from the cassette tape. Note that you must manually save
|
||||
the file using the +cassette save+ command (described below).
|
||||
|
||||
[WARNING]
|
||||
Unloading an unsaved file will lose any changes made to the file, without warning.
|
||||
This loads an existing WAVE file (from the host computer) containing a cassette tape image
|
||||
onto the CASSETTE IN port,
|
||||
in preparation for loading the program from it. If +file-path+
|
||||
is omitted, a dialog box will be presented to select the file to load.
|
||||
The tape is automatically positioned at the first header tone.
|
||||
|
||||
+cassette rewind+
|
||||
|
||||
This command ``rewinds'' the cassette tape, positioning it at the beginning
|
||||
of the tape (for subsequent reading). You do not need to rewind the tape
|
||||
before saving or unloading it, of course.
|
||||
This command rewinds the tape currently on the CASSETTE IN port.
|
||||
This command first positions the tape to its beginning,
|
||||
and then ``fast-forwards'' to the first header tone.
|
||||
|
||||
+cassette blank <file-path>+
|
||||
|
||||
This creates a new empty file (on the host computer) that represents a cassette tape image,
|
||||
and loads it onto the CASSETTE OUT port,
|
||||
in preparation for saving a program to it.
|
||||
The file must not already exist. The file type should be +.wav+ to indicate a WAVE format file.
|
||||
|
||||
+cassette save+
|
||||
|
||||
This command saves the changed tape to the file. Note that the display will show
|
||||
an asterisk +*+ next to the file name if there are unsaved changes that need to
|
||||
be saved. Unsaved changes will be lost without warning if the file is unloaded
|
||||
or if you quit the program.
|
||||
be saved.
|
||||
|
||||
+cassette eject { in | out }+
|
||||
|
||||
This removes the file from the specified cassette port (CASSETTE IN port, or CASSETE OUT port).
|
||||
|
||||
==== Example of Saving to Tape
|
||||
|
||||
@ -70,8 +94,6 @@ are going to save to a cassette tape image file.
|
||||
|
||||
]RUN
|
||||
HELLO
|
||||
|
||||
]
|
||||
------------------------
|
||||
|
||||
We first need to load a tape image file into the cassette machine.
|
||||
@ -79,24 +101,22 @@ Enter command mode by pressing +F5+, then make a new tape
|
||||
image file.
|
||||
|
||||
------------------------
|
||||
command: cassette new hello.tap
|
||||
command: cassette blank hello.wav
|
||||
------------------------
|
||||
|
||||
This will create a new, empty tape file image named +hello.tap+
|
||||
This will create a new, empty tape file image named +hello.wav+
|
||||
in the current default directory. (We could have specified a full path
|
||||
name for the file if we wanted to place it in a different directory.)
|
||||
Notice that the emulator now displays the name of the tape image file,
|
||||
along with the position and length of the tape image, which is now +0/0+.
|
||||
Notice that the emulator now displays the name of the tape image file.
|
||||
|
||||
Next, we tell Applesoft to save the program to the cassette. For this,
|
||||
we just use the +SAVE+ command. Note that this is not the
|
||||
DOS +SAVE+ command; the DOS command has a file name after
|
||||
+SAVE+. We just use +SAVE+ with no file name.
|
||||
|
||||
[source,vbs]
|
||||
------------------------
|
||||
]SAVE
|
||||
|
||||
]
|
||||
------------------------
|
||||
|
||||
It will take 10 seconds or so for it to save. Notice that the
|
||||
@ -109,11 +129,19 @@ the tape image file.
|
||||
command: cassette save
|
||||
------------------------
|
||||
|
||||
We can now unload the file from the emulator (which is like ejecting
|
||||
the tape from the cassette player).
|
||||
Now do a NEW to clear the program, and LIST to prove that it's gone.
|
||||
|
||||
[source,vbs]
|
||||
------------------------
|
||||
]NEW
|
||||
|
||||
]LIST
|
||||
------------------------
|
||||
|
||||
We can now eject the tape (close the file).
|
||||
|
||||
------------------------
|
||||
command: cassette unload
|
||||
command: cassette eject out
|
||||
------------------------
|
||||
|
||||
==== Example of Loading from Tape
|
||||
@ -123,25 +151,26 @@ we will need to first load the tape image file back into the cassette machine.
|
||||
Press +F5+ to enter command mode and load the image file.
|
||||
|
||||
------------------------
|
||||
command: cassette load hello.tap
|
||||
command: cassette load
|
||||
------------------------
|
||||
|
||||
This will load hello.tap (in the current default directory). Notice the
|
||||
This will bring up the Open File dialog box. Choose
|
||||
hello.wav file you just saved. Notice the
|
||||
emulator now displays the name of the tape image file, along with the
|
||||
position and length of the tape image, which in this case is +0/33481+.
|
||||
position and length of the tape image. Notice the emulator automatically
|
||||
advances the tape to the first header section.
|
||||
|
||||
Next, we tell Applesoft to load the program from the cassette. For this,
|
||||
we just use the +LOAD+ command. Note that this is not the
|
||||
DOS +LOAD+ command; the DOS command has a file name after
|
||||
+LOAD+. We just use +LOAD+ with no file name.
|
||||
|
||||
[source,vbs]
|
||||
------------------------
|
||||
]LOAD
|
||||
|
||||
]
|
||||
------------------------
|
||||
|
||||
It will take 10 seconds or so for it to load. Notice that the
|
||||
It will take several seconds for it to load. Notice that the
|
||||
current position of the tape is counting up as the Apple loads
|
||||
the program. When it is finished, the program will be loaded.
|
||||
|
||||
@ -154,37 +183,4 @@ the program. When it is finished, the program will be loaded.
|
||||
|
||||
]RUN
|
||||
HELLO
|
||||
|
||||
]
|
||||
------------------------
|
||||
|
||||
==== Tape Image File Format
|
||||
|
||||
The format of the tape image file is unique to the Epple ][
|
||||
It is stored in a low-level format that represents the waveform that the Apple writes
|
||||
to the cassette tape.
|
||||
|
||||
The file is a binary format. Each byte in the file represents the length of one half of one cycle
|
||||
(of voltage level variation) written to the tape. The length is in 10-microsecond units.
|
||||
|
||||
For example, a tape image file might have the following binary bytes (in decimal):
|
||||
+65 65 65 65 65 20 25 50 50 25 25 25 25 50 50+
|
||||
Since each byte represents a 10-microsecond unit, these bytes represent the following
|
||||
half-cycle lengths in microseconds:
|
||||
+650 650 650 650 650 200 250 500 500 250 250 250 250 500 500+
|
||||
The meaning of these half-cycle lengths to the Apple is as follows:
|
||||
|
||||
------------------------
|
||||
|-------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|
|
||||
------------------------
|
||||
|
||||
where +HEADER+ is a header section the Apple writes (to skip any
|
||||
unrecordable leader section on a real cassette tape); +sync+ is a
|
||||
synchronization cycle; and the subsequent cycles are the actual
|
||||
bits of data saved on the tape. A 500-microsecond cycle (which
|
||||
is stored in the file as two 250 microsecond half-cycles)
|
||||
represents a *zero* bit, and a 1-millisecond cycle (which is
|
||||
stored in the file as two 500 microsecond half-cycles)
|
||||
represents a *one* bit.
|
||||
|
@ -168,18 +168,16 @@ The +cassette+ command performs various operations of the emulated cassette tape
|
||||
|
||||
|
||||
--------
|
||||
cassette new <file-path>
|
||||
cassette load <file-path>
|
||||
cassette unload
|
||||
cassette load [ <file-path> ]
|
||||
cassette rewind
|
||||
cassette blank <file-path>
|
||||
cassette save
|
||||
cassette eject { in | out }
|
||||
--------
|
||||
|
||||
+<file-path>+ File path of the (special format) cassette tape image file.
|
||||
+<file-path>+ File path of the cassette tape image file, a standard WAVE file.
|
||||
|
||||
The +cassette+ command allows the user to control the emulated cassette tape player.
|
||||
See <<cassette_tape>> for more information about operating the emulated
|
||||
cassette player and cassette tape image files.
|
||||
See <<cassette_tape>> for more information about operating the emulated cassette tape interface.
|
||||
|
||||
|
||||
|
||||
@ -198,6 +196,5 @@ revision <rev>
|
||||
|
||||
The +revision+ command chooses which revision of the Apple ][ motherboard to
|
||||
use. The only revisions that make any difference (for now, at least) are 0 or 1. Zero
|
||||
is the original (now quite rare) version of the motherboard, that only had two hi-res
|
||||
is the original (rare) version of the motherboard, that only had two hi-res
|
||||
colors (green and purple), and always displayed text with green and purple fringes.
|
||||
Revision 1 and later motherboards are the ones we are more familiar with.
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <cstdlib>
|
||||
#include "tinyfiledialogs.h"
|
||||
#include "cassette.h"
|
||||
#include "e2const.h"
|
||||
|
||||
Cassette::Cassette(ScreenImage& gui):
|
||||
gui(gui),
|
||||
@ -45,7 +46,7 @@ void Cassette::tick() {
|
||||
* Automatically stop the tape if the Apple doesn't use
|
||||
* it within the given number of cycles.
|
||||
*/
|
||||
if (this->t_active+100000 <= this->t) {
|
||||
if (this->t_active+3*E2Const::AVG_CPU_HZ <= this->t) {
|
||||
note("STOP");
|
||||
std::cout << "cassette: t=" << this->t << std::endl;
|
||||
this->playing = false;
|
||||
|
@ -83,6 +83,7 @@ void CassetteIn::tick() {
|
||||
this->playing = false;
|
||||
this->gui.setCassettePos(this->samp_siz,this->samp_siz);
|
||||
note_pos();
|
||||
return;
|
||||
}
|
||||
|
||||
this->gui.setCassettePos(p,this->samp_siz);
|
||||
@ -229,12 +230,12 @@ bool CassetteIn::load(const std::string& filePath) {
|
||||
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);
|
||||
std::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;
|
||||
this->samp_siz = cvt.len_cvt/4u;
|
||||
|
||||
note("LOAD");
|
||||
note_pos();
|
||||
@ -250,7 +251,7 @@ bool CassetteIn::load(const std::string& filePath) {
|
||||
bool CassetteIn::eject() {
|
||||
const bool ok = Cassette::eject();
|
||||
if (ok) {
|
||||
this->gui.setCassetteInFile("(no tape)");
|
||||
this->gui.setCassetteInFile("[empty]");
|
||||
this->gui.setCassettePos(0,0);
|
||||
std::free(this->samp);
|
||||
this->samp_siz = 0;
|
||||
|
@ -104,7 +104,7 @@ bool CassetteOut::blank(const std::string& filePath) {
|
||||
bool CassetteOut::eject() {
|
||||
const bool ok = Cassette::eject();
|
||||
if (ok) {
|
||||
this->gui.setCassetteOutFile("(no tape)");
|
||||
this->gui.setCassetteOutFile("[empty]");
|
||||
this->samp_out.clear();
|
||||
}
|
||||
return ok;
|
||||
|
@ -444,6 +444,10 @@ void Emulator::processCommand() {
|
||||
}
|
||||
|
||||
bool Emulator::isSafeToQuit() {
|
||||
if (!this->apple2.cassetteOut.eject()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this->apple2.slts.isDirty()) {
|
||||
return true;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user