1
0
mirror of https://git.code.sf.net/p/fuse-emulator/fuse synced 2026-01-27 01:41:34 +03:00
Files
fuse/tape.c
Fredrick Meunier 6c20883b1f Minor reformatting
2018-07-03 08:52:18 +10:00

1092 lines
29 KiB
C

/* tape.c: tape handling routines
Copyright (c) 1999-2016 Philip Kendall, Darren Salt, Witold Filipczyk
Copyright (c) 2015 UB880D
Copyright (c) 2016 Fredrick Meunier
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 2 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, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Author contact information:
E-mail: philip-fuse@shadowmagic.org.uk
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libspectrum.h>
#include "debugger/debugger.h"
#include "event.h"
#include "fuse.h"
#include "infrastructure/startup_manager.h"
#include "loader.h"
#include "machine.h"
#include "memory_pages.h"
#include "peripherals/ula.h"
#include "phantom_typist.h"
#include "rzx.h"
#include "settings.h"
#include "sound.h"
#include "snapshot.h"
#include "tape.h"
#include "timer/timer.h"
#include "ui/ui.h"
#include "utils.h"
#include "z80/z80.h"
#include "z80/z80_macros.h"
/* The current tape */
static libspectrum_tape *tape;
/* Has the current tape been modified since it was last loaded/saved? */
int tape_modified;
/* Is the emulated tape deck playing? */
int tape_playing;
/* Was the tape playing started automatically? */
static int tape_autoplay;
/* Is there a high input to the EAR socket? */
int tape_microphone;
/* Debugger integration */
static const char * const debugger_type_string = "tape";
static const char * const play_event_detail_string = "play",
* const stop_event_detail_string = "stop";
static int play_event, stop_event = -1;
static const char * const microphone_variable_name = "microphone";
/* Spectrum events */
int tape_edge_event;
static int record_event;
static int tape_mic_off_event;
static libspectrum_dword next_tape_edge_tstates;
/* Function prototypes */
static int tape_autoload( libspectrum_machine hardware );
static int trap_load_block( libspectrum_tape_block *block );
static int tape_play( int autoplay );
static void make_name( unsigned char *name, const unsigned char *data );
static void
tape_event_record_sample( libspectrum_dword last_tstates, int type,
void *user_data );
static void tape_stop_mic_off( libspectrum_dword last_tstates, int type,
void *user_data );
/* Function definitions */
static libspectrum_dword
get_microphone( void )
{
return tape_microphone;
}
static void
next_edge( libspectrum_dword last_tstates, int type, void *user_data )
{
tape_next_edge( last_tstates, 0 );
}
static int
tape_init( void *context )
{
tape = libspectrum_tape_alloc();
play_event = debugger_event_register( debugger_type_string,
play_event_detail_string );
stop_event = debugger_event_register( debugger_type_string,
stop_event_detail_string );
debugger_system_variable_register( debugger_type_string,
microphone_variable_name, get_microphone, NULL );
tape_edge_event = event_register( next_edge, "Tape edge" );
tape_mic_off_event = event_register( tape_stop_mic_off, "Tape stop MIC off" );
record_event = event_register( tape_event_record_sample,
"Tape sample record" );
tape_modified = 0;
/* Don't call tape_stop() here as the UI hasn't been initialised yet,
so we can't update the statusbar */
tape_playing = 0;
tape_microphone = 0;
next_tape_edge_tstates = 0;
return 0;
}
static void
tape_end( void )
{
libspectrum_tape_free( tape );
tape = NULL;
}
void
tape_register_startup( void )
{
startup_manager_module dependencies[] = {
STARTUP_MANAGER_MODULE_DEBUGGER,
STARTUP_MANAGER_MODULE_EVENT,
STARTUP_MANAGER_MODULE_SETUID,
};
startup_manager_register( STARTUP_MANAGER_MODULE_TAPE, dependencies,
ARRAY_SIZE( dependencies ), tape_init, NULL,
tape_end );
}
int
tape_open( const char *filename, int autoload )
{
utils_file file;
int error;
error = utils_read_file( filename, &file );
if( error ) return error;
error = tape_read_buffer( file.buffer, file.length, LIBSPECTRUM_ID_UNKNOWN,
filename, autoload );
if( error ) { utils_close_file( &file ); return error; }
utils_close_file( &file );
return 0;
}
/* Use an already open tape file as the current tape */
int
tape_read_buffer( unsigned char *buffer, size_t length, libspectrum_id_t type,
const char *filename, int autoload )
{
int error;
if( libspectrum_tape_present( tape ) ) {
error = tape_close(); if( error ) return error;
}
error = libspectrum_tape_read( tape, buffer, length, type, filename );
if( error ) return error;
tape_modified = 0;
ui_tape_browser_update( UI_TAPE_BROWSER_NEW_TAPE, NULL );
if( autoload ) {
error = tape_autoload( machine_current->machine );
if( error ) return error;
}
return 0;
}
static int
does_tape_load_with_code( void )
{
libspectrum_tape_block *block;
libspectrum_tape_iterator iterator;
int needs_code = 0;
for( block = libspectrum_tape_iterator_init( &iterator, tape );
block;
block = libspectrum_tape_iterator_next( &iterator ) ) {
libspectrum_tape_type block_type;
size_t block_length;
libspectrum_byte *data;
/* Skip over blocks until we find one which might be a header */
block_type = libspectrum_tape_block_type( block );
if( block_type != LIBSPECTRUM_TAPE_BLOCK_ROM &&
block_type != LIBSPECTRUM_TAPE_BLOCK_DATA_BLOCK )
continue;
/* For this to be a CODE block, the block must have the right
length, it must have the header flag set and it must indicate
a CODE block */
block_length = libspectrum_tape_block_data_length( block );
data = libspectrum_tape_block_data( block );
needs_code =
(block_length == 19) &&
(data[0] == 0x00) &&
(data[1] == 0x03);
/* Stop looking now - either we found an appropriate block or we found
something strange, in which case we'll just assume it loads normally */
break;
}
return needs_code;
}
/* Load a snap to start the current tape autoloading */
static int
tape_autoload( libspectrum_machine hardware )
{
int needs_code = does_tape_load_with_code();
machine_reset( 0 );
phantom_typist_activate( hardware, needs_code );
return 0;
}
/* Close the active tape file */
int
tape_close( void )
{
int error;
ui_confirm_save_t confirm;
/* If the tape has been modified, check if we want to do this */
if( tape_modified ) {
confirm =
ui_confirm_save( "Tape has been modified.\nDo you want to save it?" );
switch( confirm ) {
case UI_CONFIRM_SAVE_SAVE:
error = ui_tape_write(); if( error ) return error;
break;
case UI_CONFIRM_SAVE_DONTSAVE: break;
case UI_CONFIRM_SAVE_CANCEL: return 1;
}
}
/* Stop the tape if it's currently playing */
if( tape_playing ) {
error = tape_stop();
if( error ) return error;
}
/* And then remove it from memory */
error = libspectrum_tape_clear( tape );
if( error ) return error;
tape_modified = 0;
ui_tape_browser_update( UI_TAPE_BROWSER_NEW_TAPE, NULL );
return 0;
}
/* Rewind to block 0, if any */
int
tape_rewind( void )
{
if( !libspectrum_tape_present( tape ) ) return 0;
return tape_select_block( 0 );
}
/* Select the nth block on the tape; 0 => 1st block */
int
tape_select_block( size_t n )
{
int error;
error = tape_select_block_no_update( n ); if( error ) return error;
ui_tape_browser_update( UI_TAPE_BROWSER_SELECT_BLOCK, NULL );
return 0;
}
/* The same, but without updating the browser display */
int
tape_select_block_no_update( size_t n )
{
return libspectrum_tape_nth_block( tape, n );
}
/* Which block is current? */
int
tape_get_current_block( void )
{
int n;
libspectrum_error error;
if( !libspectrum_tape_present( tape ) ) return -1;
error = libspectrum_tape_position( &n, tape );
if( error ) return -1;
return n;
}
/* Write the current in-memory tape file out to disk */
int
tape_write( const char* filename )
{
libspectrum_id_t type;
libspectrum_class_t class;
libspectrum_byte *buffer; size_t length;
int error;
/* Work out what sort of file we want from the filename; default to
.tzx if we couldn't guess */
error = libspectrum_identify_file_with_class( &type, &class, filename, NULL,
0 );
if( error ) return error;
if( class != LIBSPECTRUM_CLASS_TAPE || type == LIBSPECTRUM_ID_UNKNOWN )
type = LIBSPECTRUM_ID_TAPE_TZX;
length = 0;
error = libspectrum_tape_write( &buffer, &length, tape, type );
if( error != LIBSPECTRUM_ERROR_NONE ) return error;
error = utils_write_file( filename, buffer, length );
if( error ) { libspectrum_free( buffer ); return error; }
tape_modified = 0;
ui_tape_browser_update( UI_TAPE_BROWSER_MODIFIED, NULL );
libspectrum_free( buffer );
return 0;
}
int tape_can_autoload( void )
{
return( settings_current.auto_load && !memory_custom_rom() );
}
/* Load the next tape block into memory; returns 0 if a block was
loaded (even if it had an tape loading error or equivalent) or
non-zero if there was an error at the emulator level, or tape traps
are not active */
int
tape_load_trap( void )
{
libspectrum_tape_block *block, *next_block;
int error;
/* Do nothing if tape traps aren't active, or the tape is already playing */
if( !settings_current.tape_traps || tape_playing ||
rzx_playback || rzx_recording )
return 2;
/* Do nothing if we're not in the correct ROM */
if( !trap_check_rom( CHECK_TAPE_ROM ) ) return 3;
/* Return with error if no tape file loaded */
if( !libspectrum_tape_present( tape ) ) return 1;
block = libspectrum_tape_current_block( tape );
/* Skip over any meta-data blocks */
while( libspectrum_tape_block_metadata( block ) ) {
block = libspectrum_tape_select_next_block( tape );
if( !block ) return 1;
}
/* If this block isn't a ROM loader, start the block playing. After
that, return with `error' so that we actually do whichever
instruction it was that caused the trap to hit */
if( libspectrum_tape_block_type( block ) != LIBSPECTRUM_TAPE_BLOCK_ROM ||
libspectrum_tape_state( tape ) != LIBSPECTRUM_TAPE_STATE_PILOT ) {
tape_play( 1 );
return -1;
}
/* We don't properly handle the case of partial loading, so don't run
the traps in that situation */
if( libspectrum_tape_block_data_length( block ) != DE + 2 ) {
tape_play( 1 );
return -1;
}
/* Deactivate the phantom typist */
phantom_typist_deactivate();
/* All returns made via the RET at #05E2, except on Timex 2068 at #0136 */
if ( machine_current->machine == LIBSPECTRUM_MACHINE_TC2068 ||
machine_current->machine == LIBSPECTRUM_MACHINE_TS2068 ) {
PC = 0x0136;
} else {
PC = 0x05e2;
}
error = trap_load_block( block );
if( error ) return error;
/* Peek at the next block. If it's a ROM block, move along, initialise
the block, and return */
next_block = libspectrum_tape_peek_next_block( tape );
if( libspectrum_tape_block_type(next_block) == LIBSPECTRUM_TAPE_BLOCK_ROM ) {
next_block = libspectrum_tape_select_next_block( tape );
if( !next_block ) return 1;
ui_tape_browser_update( UI_TAPE_BROWSER_SELECT_BLOCK, NULL );
return 0;
}
/* If the next block isn't a ROM block, set ourselves up such that the
next thing to occur is the pause at the end of the current block */
libspectrum_tape_set_state( tape, LIBSPECTRUM_TAPE_STATE_PAUSE );
return 0;
}
static int
trap_load_block( libspectrum_tape_block *block )
{
libspectrum_byte parity, *data;
int i = 0, length, read, verify;
/* On exit:
* A = calculated parity byte if parity checked, else 0 (CHECKME)
* F : if parity checked, all flags are modified
* else carry only is modified (FIXME)
* B = 0xB0 (success) or 0x00 (failure)
* C = 0x01 (confirmed), 0x21, 0xFE or 0xDE (CHECKME)
* DE : decremented by number of bytes loaded or verified
* H = calculated parity byte or undefined
* L = last byte read, or 1 if none
* IX : incremented by number of bytes loaded or verified
* A' = unchanged on error + no flag byte, else 0x01
* F' = 0x01 on error + no flag byte, else 0x45
* R = no point in altering it :-)
* Other registers unchanged.
*/
data = libspectrum_tape_block_data( block );
length = libspectrum_tape_block_data_length( block );
/* Number of bytes to load or verify */
read = length - 1;
if( read > DE )
read = DE;
/* If there's no data in the block (!), set L then error exit.
* We don't need to alter H, IX or DE here */
if( !length ) {
L = F_ = 1;
F &= ~FLAG_C;
return 0;
}
verify = !(F_ & FLAG_C);
i = A_; /* i = A' (flag byte) */
A = 0;
/* Initialise the parity check and L to the block ID byte */
L = parity = *data++;
/* emulate zero length block rom bug */
if (!DE) {
i = 0; /* one byte was read, but it is not treated as data byte */
B = 0xB0; /* B is set to 0xB0 at the end of LD-8-BITS/0x05CA loop */
A = parity; /* rom 0x05DF */
CP( 1 ); /* parity check is successful if A==0 */
goto common_ret;
}
AF_ = 0x0145;
/* If the block ID byte != the flag byte, clear carry and return */
if( parity != i )
goto error_ret;
/* Now set L to the *last* byte in the block */
L = data[read - 1];
/* Loading or verifying determined by the carry flag of F' */
if( verify ) { /* verifying */
for( i = 0; i < read; i++ ) {
parity ^= data[i];
if( data[i] != readbyte_internal(IX+i) ) {
/* Verification failure */
L = data[i];
goto error_ret;
}
}
} else {
for( i = 0; i < read; i++ ) {
parity ^= data[i];
writebyte_internal( IX+i, data[i] );
}
}
/* At this point, i == number of bytes actually read or verified */
/* If |DE| bytes have been read and there's more data, do the parity check */
if( DE == i && read + 1 < length ) {
parity ^= data[read];
A = parity;
CP( 1 ); /* parity check is successful if A==0 */
B = 0xB0;
} else {
/* Failure to read first bit of the next byte (ref. 48K ROM, 0x5EC) */
B = 255;
L = 1;
INC( B );
error_ret:
F &= ~FLAG_C;
}
common_ret:
/* At this point, AF, AF', B and L are already modified */
C = 1;
H = parity;
DE -= i;
IX += i;
return 0;
}
/* Append to the current tape file in memory; returns 0 if a block was
saved or non-zero if there was an error at the emulator level, or tape
traps are not active */
int
tape_save_trap( void )
{
libspectrum_tape_block *block;
libspectrum_byte parity, *data;
size_t length;
int i;
/* Do nothing if tape traps aren't active */
if( !settings_current.tape_traps || tape_recording ||
rzx_playback || rzx_recording )
return 2;
/* Check we're in the right ROM */
if( !trap_check_rom( CHECK_TAPE_ROM ) ) return 3;
block = libspectrum_tape_block_alloc( LIBSPECTRUM_TAPE_BLOCK_ROM );
/* The +2 here is for the flag and parity bytes */
length = DE + 2;
libspectrum_tape_block_set_data_length( block, length );
data = libspectrum_new( libspectrum_byte, length );
libspectrum_tape_block_set_data( block, data );
/* First, store the flag byte (and initialise the parity counter) */
data[0] = parity = A;
/* then the main body of the data, counting parity along the way */
for( i=0; i<DE; i++) {
libspectrum_byte b = readbyte_internal( IX+i );
parity ^= b;
data[i+1] = b;
}
/* And finally the parity byte */
data[ DE+1 ] = parity;
/* Give a 1 second pause after this block */
libspectrum_tape_block_set_pause( block, 1000 );
libspectrum_tape_append_block( tape, block );
tape_modified = 1;
ui_tape_browser_update( UI_TAPE_BROWSER_NEW_BLOCK, block );
/* And then return via the RET at #053E, except on Timex 2068 at #00E4 */
if ( machine_current->machine == LIBSPECTRUM_MACHINE_TC2068 ||
machine_current->machine == LIBSPECTRUM_MACHINE_TS2068 ) {
PC = 0x00e4;
} else {
PC = 0x053e;
}
return 0;
}
static int
tape_play( int autoplay )
{
if( !libspectrum_tape_present( tape ) ) return 1;
/* Otherwise, start the tape going */
tape_playing = 1;
tape_autoplay = autoplay;
tape_microphone = 0;
event_remove_type( tape_mic_off_event );
/* Update the status bar */
ui_statusbar_update( UI_STATUSBAR_ITEM_TAPE, UI_STATUSBAR_STATE_ACTIVE );
timer_start_fastloading();
loader_tape_play();
event_add( tstates + next_tape_edge_tstates, tape_edge_event );
next_tape_edge_tstates = 0;
/* Once the tape has started, the phantom typist has done its job so
cancel any pending actions */
phantom_typist_deactivate();
debugger_event( play_event );
return 0;
}
int
tape_do_play( int autoplay )
{
if( !tape_playing ) {
return tape_play( autoplay );
} else {
return 0;
}
}
int
tape_toggle_play( int autoplay )
{
if( tape_playing ) {
return tape_stop();
} else {
return tape_play( autoplay );
}
}
static void
save_next_tape_edge( gpointer data, gpointer user_data )
{
event_t *ptr = data;
if( ptr->type == tape_edge_event ) {
next_tape_edge_tstates = ptr->tstates - tstates;
}
}
static void
tape_save_next_edge( void )
{
event_foreach( save_next_tape_edge, NULL );
}
int
tape_stop( void )
{
if( tape_playing ) {
tape_playing = 0;
ui_statusbar_update( UI_STATUSBAR_ITEM_TAPE, UI_STATUSBAR_STATE_INACTIVE );
loader_tape_stop();
timer_stop_fastloading();
tape_save_next_edge();
event_remove_type( tape_edge_event );
/* Turn off any lingering MIC level in a second (some loaders like Alkatraz
seem to check the MIC level soon after loading is finished, presumably as
a copy protection check */
event_add( tstates + machine_current->timings.tstates_per_frame,
tape_mic_off_event );
}
if( stop_event != -1 ) debugger_event( stop_event );
return 0;
}
int
tape_is_playing( void )
{
return tape_playing;
}
int
tape_present( void )
{
return libspectrum_tape_present( tape );
}
typedef struct
{
libspectrum_byte *tape_buffer;
libspectrum_dword tape_buffer_size;
libspectrum_dword tape_buffer_used;
int tstates_per_sample;
int last_level;
int last_level_count;
} tape_rec_state;
int tape_recording = 0;
static tape_rec_state rec_state;
void
tape_record_start( void )
{
/* sample rate will be 44.1KHz */
rec_state.tstates_per_sample =
machine_current->timings.processor_speed/44100;
rec_state.tape_buffer_size = 8192;
rec_state.tape_buffer = libspectrum_new(libspectrum_byte,
rec_state.tape_buffer_size);
rec_state.tape_buffer_used = 0;
/* start scheduling events that record into a buffer that we
start allocating here */
event_add( tstates + rec_state.tstates_per_sample, record_event );
rec_state.last_level = ula_tape_level();
rec_state.last_level_count = 1;
tape_recording = 1;
/* Also want to disable other tape actions */
ui_menu_activate( UI_MENU_ITEM_TAPE_RECORDING, 1 );
}
static int
write_rec_buffer( libspectrum_byte *tape_buffer,
libspectrum_dword tape_buffer_used,
int last_level_count )
{
if( last_level_count <= 0xff ) {
tape_buffer[ tape_buffer_used++ ] = last_level_count;
} else {
tape_buffer[ tape_buffer_used++ ] = 0;
tape_buffer[ tape_buffer_used++ ] = ( last_level_count & 0x000000ff ) ;
tape_buffer[ tape_buffer_used++ ] = ( last_level_count & 0x0000ff00 ) >> 8;
tape_buffer[ tape_buffer_used++ ] = ( last_level_count & 0x00ff0000 ) >> 16;
tape_buffer[ tape_buffer_used++ ] = ( last_level_count & 0xff000000 ) >> 24;
}
return tape_buffer_used;
}
void
tape_event_record_sample( libspectrum_dword last_tstates, int type,
void *user_data )
{
if( rec_state.last_level != (ula_tape_level()) ) {
/* put a sample into the recording buffer */
rec_state.tape_buffer_used =
write_rec_buffer( rec_state.tape_buffer,
rec_state.tape_buffer_used,
rec_state.last_level_count );
rec_state.last_level_count = 0;
rec_state.last_level = ula_tape_level();
/* make sure we can still fit a dword and a flag byte in the buffer */
if( rec_state.tape_buffer_used+5 >= rec_state.tape_buffer_size ) {
rec_state.tape_buffer_size = rec_state.tape_buffer_size*2;
rec_state.tape_buffer =
libspectrum_renew( libspectrum_byte, rec_state.tape_buffer,
rec_state.tape_buffer_size );
}
}
rec_state.last_level_count++;
/* schedule next timer */
event_add( last_tstates + rec_state.tstates_per_sample, record_event );
}
int
tape_record_stop( void )
{
libspectrum_tape_block* block;
/* put last sample into the recording buffer */
rec_state.tape_buffer_used = write_rec_buffer( rec_state.tape_buffer,
rec_state.tape_buffer_used,
rec_state.last_level_count );
/* stop scheduling events and turn buffer into a block and
pop into the current tape */
event_remove_type( record_event );
block = libspectrum_tape_block_alloc( LIBSPECTRUM_TAPE_BLOCK_RLE_PULSE );
libspectrum_tape_block_set_scale( block, rec_state.tstates_per_sample );
libspectrum_tape_block_set_data_length( block, rec_state.tape_buffer_used );
libspectrum_tape_block_set_data( block, rec_state.tape_buffer );
libspectrum_tape_append_block( tape, block );
rec_state.tape_buffer = NULL;
rec_state.tape_buffer_size = 0;
rec_state.tape_buffer_used = 0;
tape_modified = 1;
ui_tape_browser_update( UI_TAPE_BROWSER_NEW_BLOCK, block );
tape_recording = 0;
/* Also want to reenable other tape actions */
ui_menu_activate( UI_MENU_ITEM_TAPE_RECORDING, 0 );
return 0;
}
void
tape_next_edge( libspectrum_dword last_tstates, int from_acceleration )
{
libspectrum_error libspec_error;
libspectrum_tape_block *block;
libspectrum_dword edge_tstates;
int flags;
/* If the tape's not playing, just return */
if( ! tape_playing ) return;
/* Get the time until the next edge */
libspec_error = libspectrum_tape_get_next_edge( &edge_tstates, &flags,
tape );
if( libspec_error != LIBSPECTRUM_ERROR_NONE ) return;
/* Invert the microphone state */
if( edge_tstates ||
!( flags & LIBSPECTRUM_TAPE_FLAGS_NO_EDGE ) ||
( flags & ( LIBSPECTRUM_TAPE_FLAGS_STOP |
LIBSPECTRUM_TAPE_FLAGS_LEVEL_LOW |
LIBSPECTRUM_TAPE_FLAGS_LEVEL_HIGH ) ) ) {
if( flags & LIBSPECTRUM_TAPE_FLAGS_NO_EDGE ) {
/* Do nothing */
} else if( flags & LIBSPECTRUM_TAPE_FLAGS_LEVEL_LOW ) {
tape_microphone = 0;
} else if( flags & LIBSPECTRUM_TAPE_FLAGS_LEVEL_HIGH ) {
tape_microphone = 1;
} else {
tape_microphone = !tape_microphone;
}
}
sound_beeper( last_tstates, tape_microphone );
/* If we've been requested to stop the tape, do so and then
return without stacking another edge */
if( ( flags & LIBSPECTRUM_TAPE_FLAGS_STOP ) ||
( ( flags & LIBSPECTRUM_TAPE_FLAGS_STOP48 ) &&
( !( libspectrum_machine_capabilities( machine_current->machine ) &
LIBSPECTRUM_MACHINE_CAPABILITY_128_MEMORY
)
)
)
)
{
tape_stop();
return;
}
/* If that was the end of a block, update the browser */
if( flags & LIBSPECTRUM_TAPE_FLAGS_BLOCK ) {
ui_tape_browser_update( UI_TAPE_BROWSER_SELECT_BLOCK, NULL );
/* If the tape was started automatically, tape traps are active
and the new block is a ROM loader, stop the tape and return
without putting another event into the queue */
block = libspectrum_tape_current_block( tape );
if( tape_autoplay && settings_current.tape_traps && !rzx_recording &&
libspectrum_tape_block_type( block ) == LIBSPECTRUM_TAPE_BLOCK_ROM
) {
tape_stop();
return;
}
}
/* Otherwise, put this into the event queue; remember that this edge
should occur 'edge_tstates' after the last edge, not after the
current time (these will be slightly different as we only process
events between instructions). */
event_add( last_tstates + edge_tstates, tape_edge_event );
/* Store length flags for acceleration purposes */
loader_set_acceleration_flags( flags, from_acceleration );
}
static void
tape_stop_mic_off( libspectrum_dword last_tstates, int type, void *user_data )
{
tape_microphone = 0;
}
/* Call a user-supplied function for every block in the current tape */
int
tape_foreach( void (*function)( libspectrum_tape_block *block,
void *user_data),
void *user_data )
{
libspectrum_tape_block *block;
libspectrum_tape_iterator iterator;
for( block = libspectrum_tape_iterator_init( &iterator, tape );
block;
block = libspectrum_tape_iterator_next( &iterator ) )
function( block, user_data );
return 0;
}
int
tape_block_details( char *buffer, size_t length,
libspectrum_tape_block *block )
{
libspectrum_byte *data;
const char *type; unsigned char name[11];
int offset;
size_t i;
unsigned long total_pulses;
buffer[0] = '\0';
switch( libspectrum_tape_block_type( block ) ) {
case LIBSPECTRUM_TAPE_BLOCK_ROM:
case LIBSPECTRUM_TAPE_BLOCK_DATA_BLOCK:
/* See if this looks like a standard Spectrum header and if so
display some extra data */
if( libspectrum_tape_block_data_length( block ) != 19 ) goto normal;
data = libspectrum_tape_block_data( block );
/* Flag byte is 0x00 for headers */
if( data[0] != 0x00 ) goto normal;
switch( data[1] ) {
case 0x00: type = "Program"; break;
case 0x01: type = "Number array"; break;
case 0x02: type = "Character array"; break;
case 0x03: type = "Bytes"; break;
default: goto normal;
}
make_name( name, &data[2] );
snprintf( buffer, length, "%s: \"%s\"", type, name );
break;
normal:
snprintf( buffer, length, "%lu bytes",
(unsigned long)libspectrum_tape_block_data_length( block ) );
break;
case LIBSPECTRUM_TAPE_BLOCK_TURBO:
case LIBSPECTRUM_TAPE_BLOCK_PURE_DATA:
case LIBSPECTRUM_TAPE_BLOCK_RAW_DATA:
snprintf( buffer, length, "%lu bytes",
(unsigned long)libspectrum_tape_block_data_length( block ) );
break;
case LIBSPECTRUM_TAPE_BLOCK_PURE_TONE:
snprintf( buffer, length, "%lu tstates",
(unsigned long)libspectrum_tape_block_pulse_length( block ) );
break;
case LIBSPECTRUM_TAPE_BLOCK_PULSES:
snprintf( buffer, length, "%lu pulses",
(unsigned long)libspectrum_tape_block_count( block ) );
break;
case LIBSPECTRUM_TAPE_BLOCK_PULSE_SEQUENCE:
total_pulses = 0;
for( i=0; i < libspectrum_tape_block_count( block ); i++ )
total_pulses += libspectrum_tape_block_pulse_repeats( block, i );
snprintf( buffer, length, "%lu pulses", total_pulses );
break;
case LIBSPECTRUM_TAPE_BLOCK_PAUSE:
snprintf( buffer, length, "%lu ms",
(unsigned long)libspectrum_tape_block_pause( block ) );
break;
case LIBSPECTRUM_TAPE_BLOCK_GROUP_START:
case LIBSPECTRUM_TAPE_BLOCK_COMMENT:
case LIBSPECTRUM_TAPE_BLOCK_MESSAGE:
case LIBSPECTRUM_TAPE_BLOCK_CUSTOM:
snprintf( buffer, length, "%s", libspectrum_tape_block_text( block ) );
break;
case LIBSPECTRUM_TAPE_BLOCK_JUMP:
offset = libspectrum_tape_block_offset( block );
if( offset > 0 ) {
snprintf( buffer, length, "Forward %d blocks", offset );
} else {
snprintf( buffer, length, "Backward %d blocks", -offset );
}
break;
case LIBSPECTRUM_TAPE_BLOCK_LOOP_START:
snprintf( buffer, length, "%lu iterations",
(unsigned long)libspectrum_tape_block_count( block ) );
break;
case LIBSPECTRUM_TAPE_BLOCK_SELECT:
snprintf( buffer, length, "%lu options",
(unsigned long)libspectrum_tape_block_count( block ) );
break;
case LIBSPECTRUM_TAPE_BLOCK_GENERALISED_DATA:
snprintf( buffer, length, "%lu data symbols",
(unsigned long)libspectrum_tape_generalised_data_symbol_table_symbols_in_block( libspectrum_tape_block_data_table( block ) ) );
break;
case LIBSPECTRUM_TAPE_BLOCK_RLE_PULSE:
/* Could do something better with this one */
break;
case LIBSPECTRUM_TAPE_BLOCK_GROUP_END:
case LIBSPECTRUM_TAPE_BLOCK_LOOP_END:
case LIBSPECTRUM_TAPE_BLOCK_STOP48:
case LIBSPECTRUM_TAPE_BLOCK_SET_SIGNAL_LEVEL:
case LIBSPECTRUM_TAPE_BLOCK_ARCHIVE_INFO:
case LIBSPECTRUM_TAPE_BLOCK_HARDWARE:
case LIBSPECTRUM_TAPE_BLOCK_CONCAT:
break;
}
return 0;
}
static void
make_name( unsigned char *name, const unsigned char *data )
{
size_t i;
for( i = 0; i < 10; i++, name++, data++ ) {
if( *data >= 32 && *data < 127 ) {
*name = *data;
} else {
*name = '?';
}
}
*name = '\0';
}