diff --git a/doc/cassette.asciidoc b/doc/cassette.asciidoc index 3c96149..e40c706 100644 --- a/doc/cassette.asciidoc +++ b/doc/cassette.asciidoc @@ -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 + 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 + ++cassette load [ ]+ -This creates a new empty file (on the host computer) that represents a cassette tape image. -The file must not already exist. - -+cassette load + - -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 + + +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. diff --git a/doc/commands.asciidoc b/doc/commands.asciidoc index 8266ae7..b803723 100644 --- a/doc/commands.asciidoc +++ b/doc/commands.asciidoc @@ -168,18 +168,16 @@ The +cassette+ command performs various operations of the emulated cassette tape -------- -cassette new -cassette load -cassette unload +cassette load [ ] cassette rewind +cassette blank cassette save +cassette eject { in | out } -------- -++ File path of the (special format) cassette tape image file. +++ 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 <> for more information about operating the emulated -cassette player and cassette tape image files. +See <> for more information about operating the emulated cassette tape interface. @@ -198,6 +196,5 @@ revision 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. diff --git a/src/cassette.cpp b/src/cassette.cpp index 698e830..b2469c1 100644 --- a/src/cassette.cpp +++ b/src/cassette.cpp @@ -21,6 +21,7 @@ #include #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; diff --git a/src/cassettein.cpp b/src/cassettein.cpp index 55c870c..8e13d1d 100644 --- a/src/cassettein.cpp +++ b/src/cassettein.cpp @@ -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::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(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; diff --git a/src/cassetteout.cpp b/src/cassetteout.cpp index f2f258b..6dc5226 100644 --- a/src/cassetteout.cpp +++ b/src/cassetteout.cpp @@ -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; diff --git a/src/emulator.cpp b/src/emulator.cpp index 7d9429f..2f957e8 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -444,6 +444,10 @@ void Emulator::processCommand() { } bool Emulator::isSafeToQuit() { + if (!this->apple2.cassetteOut.eject()) { + return false; + } + if (!this->apple2.slts.isDirty()) { return true; }