1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091
|
/* 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';
}
|