/* 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; }
/* 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 ) return 2; /* Check we're in the right ROM */ if( ! trap_check_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 = malloc( length * sizeof(libspectrum_byte) ); if( !data ) { free( block ); return 1; } 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; }
/* 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; }
/* 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; }
/* 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; }
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 type, void *user_data ) { 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 && 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 ); }
/* 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 ) 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; } /* 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; }