fork of PCE focusing on macplus, supporting DaynaPort SCSI network emulation
1/*****************************************************************************
2 * pce *
3 *****************************************************************************/
4
5/*****************************************************************************
6 * File name: src/devices/cassette.c *
7 * Created: 2020-04-30 by Hampa Hug <hampa@hampa.ch> *
8 * Copyright: (C) 2020-2025 Hampa Hug <hampa@hampa.ch> *
9 *****************************************************************************/
10
11/*****************************************************************************
12 * This program is free software. You can redistribute it and / or modify it *
13 * under the terms of the GNU General Public License version 2 as published *
14 * by the Free Software Foundation. *
15 * *
16 * This program is distributed in the hope that it will be useful, but *
17 * WITHOUT ANY WARRANTY, without even the implied warranty of *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
19 * Public License for more details. *
20 *****************************************************************************/
21
22
23#include <config.h>
24
25#include <devices/cassette.h>
26
27#include <stdlib.h>
28#include <string.h>
29
30#include <drivers/pti/pti.h>
31#include <drivers/pti/pti-io.h>
32
33#include <lib/console.h>
34#include <lib/msg.h>
35#include <lib/string.h>
36#include <lib/sysdep.h>
37
38
39typedef struct {
40 const char *msg;
41
42 int (*set_msg) (cassette_t *cas, const char *msg, const char *val);
43} cas_msg_list_t;
44
45
46static
47int cas_set_msg_commit (cassette_t *cas, const char *msg, const char *val)
48{
49 if (cas_commit (cas)) {
50 return (1);
51 }
52
53 return (0);
54}
55
56static
57int cas_set_msg_create (cassette_t *cas, const char *msg, const char *val)
58{
59 if (*val == 0) {
60 return (1);
61 }
62
63 remove (val);
64
65 if (cas_set_write_name (cas, val, 1)) {
66 return (1);
67 }
68
69 if (cas->auto_play) {
70 cas_press_keys (cas, 1, 1);
71 }
72 else {
73 cas_press_keys (cas, 0, 0);
74 }
75
76 cas_print_state (cas);
77
78 return (0);
79}
80
81static
82int cas_set_msg_open (cassette_t *cas, const char *msg, const char *val)
83{
84 if (*val == 0) {
85 val = NULL;
86 }
87
88 if (cas_set_read_name (cas, NULL)) {
89 return (1);
90 }
91
92 if (cas_set_write_name (cas, val, 0)) {
93 return (1);
94 }
95
96 cas_press_keys (cas, 0, 0);
97 cas_print_state (cas);
98
99 return (0);
100}
101
102static
103int cas_set_msg_play (cassette_t *cas, const char *msg, const char *val)
104{
105 cas_press_keys (cas, 1, 0);
106 cas_print_state (cas);
107
108 return (0);
109}
110
111static
112int cas_set_msg_load (cassette_t *cas, const char *msg, const char *val)
113{
114 double v;
115
116 cas_press_keys (cas, 0, 0);
117
118 if (*val != 0) {
119 if (msg_get_double (val, &v)) {
120 return (1);
121 }
122
123 cas_set_position (cas, v);
124 }
125
126 cas_print_state (cas);
127
128 return (0);
129}
130
131static
132int cas_set_msg_read (cassette_t *cas, const char *msg, const char *val)
133{
134 if (*val == 0) {
135 val = NULL;
136 }
137
138 if (cas_set_read_name (cas, val)) {
139 return (1);
140 }
141
142 cas_press_keys (cas, 0, 0);
143 cas_print_state (cas);
144
145 return (0);
146}
147
148static
149int cas_set_msg_record (cassette_t *cas, const char *msg, const char *val)
150{
151 cas_press_keys (cas, 1, 1);
152 cas_print_state (cas);
153
154 return (0);
155}
156
157static
158int cas_set_msg_space (cassette_t *cas, const char *msg, const char *val)
159{
160 double sec;
161
162 if (*val != 0) {
163 if (msg_get_double (val, &sec)) {
164 return (1);
165 }
166 }
167 else {
168 sec = 1.0;
169 }
170
171 if (cas_space (cas, sec)) {
172 return (1);
173 }
174
175 cas_print_state (cas);
176
177 return (0);
178}
179
180static
181int cas_set_msg_state (cassette_t *cas, const char *msg, const char *val)
182{
183 cas_print_state (cas);
184
185 return (0);
186}
187
188static
189int cas_set_msg_stop (cassette_t *cas, const char *msg, const char *val)
190{
191 cas_press_keys (cas, 0, 0);
192 cas_print_state (cas);
193
194 return (0);
195}
196
197static
198int cas_set_msg_truncate (cassette_t *cas, const char *msg, const char *val)
199{
200 double v;
201
202 if (*val == 0) {
203 return (1);
204 }
205
206 if (msg_get_double (val, &v)) {
207 return (1);
208 }
209
210 if (cas_truncate (cas, v)) {
211 return (1);
212 }
213
214 cas_print_state (cas);
215
216 return (0);
217}
218
219static
220int cas_set_msg_write (cassette_t *cas, const char *msg, const char *val)
221{
222 if (*val == 0) {
223 val = NULL;
224 }
225
226 if (cas_set_write_name (cas, val, 1)) {
227 return (1);
228 }
229
230 if (cas->auto_play) {
231 cas_press_keys (cas, 1, 1);
232 }
233 else {
234 cas_press_keys (cas, 0, 0);
235 }
236
237 cas_print_state (cas);
238
239 return (0);
240}
241
242static cas_msg_list_t set_msg_list[] = {
243 { "emu.cas.commit", cas_set_msg_commit },
244 { "emu.cas.create", cas_set_msg_create },
245 { "emu.cas.c", cas_set_msg_create },
246 { "emu.cas.open", cas_set_msg_open },
247 { "emu.cas.play", cas_set_msg_play },
248 { "emu.cas.load", cas_set_msg_load },
249 { "emu.cas.read", cas_set_msg_read },
250 { "emu.cas.record", cas_set_msg_record },
251 { "emu.cas.space", cas_set_msg_space },
252 { "emu.cas.state", cas_set_msg_state },
253 { "emu.cas.stop", cas_set_msg_stop },
254 { "emu.cas.truncate", cas_set_msg_truncate },
255 { "emu.cas.write", cas_set_msg_write },
256 { NULL, NULL }
257};
258
259
260int cas_set_msg (cassette_t *cas, const char *msg, const char *val)
261{
262 cas_msg_list_t *lst;
263
264 if (val == NULL) {
265 val = "";
266 }
267
268 lst = set_msg_list;
269
270 while (lst->msg != NULL) {
271 if (msg_is_message (lst->msg, msg)) {
272 return (lst->set_msg (cas, msg, val));
273 }
274
275 lst += 1;
276 }
277
278 return (-1);
279}
280
281
282void cas_init (cassette_t *cas)
283{
284 cas->set_inp = NULL;
285 cas->set_inp_ext = NULL;
286
287 cas->set_play = NULL;
288 cas->set_play_ext = NULL;
289
290 cas->set_run = NULL;
291 cas->set_run_ext = NULL;
292
293 cas->read_img = NULL;
294 cas->read_name = NULL;
295
296 cas->write_img = NULL;
297 cas->write_name = NULL;
298
299 cas->modified = 0;
300 cas->eof = 0;
301
302 cas->run = 0;
303 cas->motor = 0;
304 cas->play = 0;
305 cas->record = 0;
306
307 cas->auto_play = 0;
308 cas->auto_motor = 0;
309
310 cas->out_val = 0;
311 cas->inp_val = 0;
312
313 cas->clock = 0;
314 cas->remainder = 0;
315 cas->position = 0;
316
317 cas->motor_delay = 0;
318 cas->motor_delay_count = 0;
319
320 cas->counter = 0;
321}
322
323void cas_free (cassette_t *cas)
324{
325 cas_commit (cas);
326
327 pti_img_del (cas->write_img);
328 pti_img_del (cas->read_img);
329
330 free (cas->write_name);
331 free (cas->read_name);
332}
333
334cassette_t *cas_new (void)
335{
336 cassette_t *cas;
337
338 if ((cas = malloc (sizeof (cassette_t))) == NULL) {
339 return (NULL);
340 }
341
342 cas_init (cas);
343
344 return (cas);
345}
346
347void cas_del (cassette_t *cas)
348{
349 if (cas != NULL) {
350 cas_free (cas);
351 }
352
353 free (cas);
354}
355
356void cas_set_inp_fct (cassette_t *cas, void *ext, void *fct)
357{
358 cas->set_inp_ext = ext;
359 cas->set_inp = fct;
360}
361
362void cas_set_play_fct (cassette_t *cas, void *ext, void *fct)
363{
364 cas->set_play_ext = ext;
365 cas->set_play = fct;
366}
367
368void cas_set_run_fct (cassette_t *cas, void *ext, void *fct)
369{
370 cas->set_run_ext = ext;
371 cas->set_run = fct;
372}
373
374void cas_set_clock (cassette_t *cas, unsigned long val)
375{
376 cas->clock = val;
377 cas->remainder = 0;
378}
379
380void cas_set_auto_play (cassette_t *cas, int val)
381{
382 cas->auto_play = (val != 0);
383}
384
385void cas_set_auto_motor (cassette_t *cas, int val)
386{
387 cas->auto_motor = (val != 0);
388}
389
390void cas_set_motor_delay (cassette_t *cas, unsigned long val)
391{
392 cas->motor_delay = val;
393}
394
395static
396pti_img_t *cas_get_read_img (const cassette_t *cas)
397{
398 if (cas->read_img != NULL) {
399 return (cas->read_img);
400 }
401 else if (cas->write_img != NULL) {
402 return (cas->write_img);
403 }
404
405 return (NULL);
406}
407
408int cas_set_read_name (cassette_t *cas, const char *fname)
409{
410 cas_set_record (cas, 0);
411 cas_set_play (cas, 0);
412
413 pti_img_del (cas->read_img);
414 cas->read_img = NULL;
415
416 free (cas->read_name);
417 cas->read_name = NULL;
418
419 cas->position = 0;
420 cas->remainder = 0;
421
422 if (fname == NULL) {
423 return (0);
424 }
425
426 if ((cas->read_name = str_copy_alloc (fname)) == NULL) {
427 return (1);
428 }
429
430 if ((cas->read_img = pti_img_load (cas->read_name, PTI_FORMAT_NONE)) == NULL) {
431 return (1);
432 }
433
434 cas->eof = 0;
435
436 return (0);
437}
438
439int cas_set_write_name (cassette_t *cas, const char *fname, int create)
440{
441 cas_set_record (cas, 0);
442 cas_set_play (cas, 0);
443
444 if (cas_commit (cas)) {
445 return (1);
446 }
447
448 pti_img_del (cas->write_img);
449 cas->write_img = NULL;
450
451 free (cas->write_name);
452 cas->write_name = NULL;
453
454 cas->modified = 0;
455 cas->position = 0;
456 cas->remainder = 0;
457
458 if (fname == NULL) {
459 return (0);
460 }
461
462 if ((cas->write_name = str_copy_alloc (fname)) == NULL) {
463 return (1);
464 }
465
466 if (create) {
467 if (pce_file_exists (fname) == 0) {
468 if ((cas->write_img = pti_img_new()) == NULL) {
469 return (1);
470 }
471
472 pti_img_set_clock (cas->write_img, cas->clock);
473
474 return (0);
475 }
476 }
477
478 if ((cas->write_img = pti_img_load (cas->write_name, PTI_FORMAT_NONE)) == NULL) {
479 return (1);
480 }
481
482 return (0);
483}
484
485int cas_commit (cassette_t *cas)
486{
487 if (cas->modified == 0) {
488 return (0);
489 }
490
491 if ((cas->write_img == NULL) || (cas->write_name == NULL)) {
492 return (1);
493 }
494
495 pce_printf ("CASSETTE writing back %s\n", cas->write_name);
496
497 if (pti_img_save (cas->write_name, cas->write_img, PTI_FORMAT_NONE)) {
498 return (1);
499 }
500
501 cas->modified = 0;
502
503 return (0);
504}
505
506int cas_get_position (const cassette_t *cas, double *tm)
507{
508 unsigned long pos, sec, clk;
509 pti_img_t *img;
510
511 if (cas->record) {
512 if ((img = cas->write_img) == NULL) {
513 return (1);
514 }
515
516 pos = img->pulse_cnt;
517 }
518 else {
519 if ((img = cas_get_read_img (cas)) == NULL) {
520 return (1);
521 }
522
523 pos = cas->position;
524 }
525
526 pti_img_get_time (img, pos, &sec, &clk);
527 pti_time_get (sec, clk, tm, img->clock);
528
529 return (0);
530}
531
532int cas_set_position (cassette_t *cas, double tm)
533{
534 unsigned long pos, sec, clk;
535 pti_img_t *img;
536
537 if ((img = cas_get_read_img (cas)) == NULL) {
538 return (1);
539 }
540
541 pti_time_set (&sec, &clk, tm, img->clock);
542 pti_img_get_index (img, &pos, sec, clk);
543
544 cas->position = pos;
545
546 cas->remainder = 0;
547 cas->eof = 0;
548
549 return (0);
550}
551
552int cas_truncate (cassette_t *cas, double tm)
553{
554 unsigned long pos, sec, clk;
555
556 if (cas->write_img == NULL) {
557 return (1);
558 }
559
560 pti_time_set (&sec, &clk, tm, cas->write_img->clock);
561 pti_img_get_index (cas->write_img, &pos, sec, clk);
562
563 if (pos < cas->write_img->pulse_cnt) {
564 cas->write_img->pulse_cnt = pos;
565 cas->modified = 1;
566 }
567
568 return (0);
569}
570
571int cas_space (cassette_t *cas, double tm)
572{
573 unsigned long sec, clk;
574
575 if (cas->write_img == NULL) {
576 return (1);
577 }
578
579 pti_time_set (&sec, &clk, tm, cas->write_img->clock);
580
581 if (pti_img_add_pulse (cas->write_img, clk, 0)) {
582 return (1);
583 }
584
585 cas->modified = 1;
586
587 return (0);
588}
589
590static
591int cas_read_pulse (cassette_t *cas, unsigned long *clk, int *level)
592{
593 pti_img_t *img;
594
595 if ((img = cas->read_img) == NULL) {
596 if ((img = cas->write_img) == NULL) {
597 return (1);
598 }
599 }
600
601 if (cas->position >= img->pulse_cnt) {
602 return (1);
603 }
604
605 pti_pulse_get (img->pulse[cas->position], clk, level);
606
607 *clk = pti_pulse_convert (*clk, cas->clock, img->clock, &cas->remainder);
608
609 cas->position += 1;
610
611 return (0);
612}
613
614static
615int cas_write_pulse (cassette_t *cas, unsigned long clk, int level)
616{
617 pti_img_t *img;
618
619 if ((img = cas->write_img) == NULL) {
620 return (1);
621 }
622
623 clk = pti_pulse_convert (clk, img->clock, cas->clock, &cas->remainder);
624
625 if (pti_img_add_pulse (img, clk, level)) {
626 return (1);
627 }
628
629 cas->modified = 1;
630
631 return (0);
632}
633
634static
635void cas_write_pulse_flush (cassette_t *cas)
636{
637 if ((cas->run == 0) || (cas->record == 0)) {
638 return;
639 }
640
641 if (cas->counter > 0) {
642 cas_write_pulse (cas, cas->counter, cas->out_val ? 1 : -1);
643 cas->counter = 0;
644 }
645}
646
647static
648void cas_run_stop (cassette_t *cas)
649{
650 int run;
651
652 run = (cas->motor && cas->play) || (cas->motor_delay_count > 0);
653
654 if (cas->run == run) {
655 return;
656 }
657
658 if (cas->run && cas->record) {
659 cas_write_pulse_flush (cas);
660 }
661
662 cas->run = run;
663
664 if (cas->set_run != NULL) {
665 cas->set_run (cas->set_run_ext, run);
666 }
667
668 if (cas->record) {
669 cas->eof = 0;
670 }
671
672 cas_print_state (cas);
673
674 if (cas->run) {
675 cas->counter = 0;
676 cas->remainder = 0;
677 }
678}
679
680void cas_set_play (cassette_t *cas, int val)
681{
682 val = (val != 0) || cas->auto_play;
683
684 if (cas->play == val) {
685 return;
686 }
687
688 cas->play = val;
689 cas->motor_delay_count = 0;
690
691 if (cas->auto_motor) {
692 cas->motor = cas->play || cas->record;
693 }
694
695 if (cas->set_play != NULL) {
696 cas->set_play (cas->set_play_ext, cas->play);
697 }
698
699 cas_run_stop (cas);
700}
701
702void cas_set_record (cassette_t *cas, int val)
703{
704 val = (val != 0);
705
706 if (cas->record == val) {
707 return;
708 }
709
710 if (cas->run && cas->record) {
711 cas_write_pulse_flush (cas);
712 }
713
714 cas->record = val;
715 cas->motor_delay_count = 0;
716
717 if (cas->auto_motor) {
718 cas->motor = cas->play || cas->record;
719 }
720
721 cas_run_stop (cas);
722}
723
724void cas_press_keys (cassette_t *cas, int play, int record)
725{
726 cas_set_record (cas, record);
727 cas_set_play (cas, play);
728}
729
730void cas_print_state (cassette_t *cas)
731{
732 pti_img_t *img1, *img2;
733 const char *state;
734 unsigned long sec, clk;
735 double tm1, tm2;
736
737 img1 = cas_get_read_img (cas);
738 img2 = cas->write_img;
739
740 tm1 = 0.0;
741 tm2 = 0.0;
742
743 if (img1 != NULL) {
744 pti_img_get_time (img1, cas->position, &sec, &clk);
745 pti_time_get (sec, clk, &tm1, img1->clock);
746 }
747
748 if (img2 != NULL) {
749 pti_img_get_length (img2, &sec, &clk);
750 pti_time_get (sec, clk, &tm2, img2->clock);
751 }
752
753 if (cas->eof) {
754 state = "EOF";
755 }
756 else if (cas->run) {
757 state = cas->record ? "SAVE" : "LOAD";
758 }
759 else {
760 state = "";
761 }
762
763 pce_printf ("CASSETTE [%c%c%c] [%05.1f] [%05.1f] %s\n",
764 cas->play ? 'P' : '-',
765 cas->record ? 'R' : '-',
766 cas->motor ? 'M' : '-',
767 tm1, tm2,
768 state
769 );
770}
771
772void cas_set_motor (cassette_t *cas, int val)
773{
774 val = (val != 0);
775
776 if (cas->auto_motor) {
777 val |= cas->play || cas->record;
778 }
779
780 if (cas->motor == val) {
781 return;
782 }
783
784 cas->motor = val;
785
786 if (cas->run && (cas->motor == 0)) {
787 cas->motor_delay_count = cas->motor_delay;
788 }
789 else {
790 cas->motor_delay_count = 0;
791 }
792
793 cas_run_stop (cas);
794}
795
796static
797void cas_set_inp (cassette_t *cas, int val)
798{
799 val = (val != 0);
800
801 if (cas->inp_val == val) {
802 return;
803 }
804
805 if (cas->set_inp != NULL) {
806 cas->set_inp (cas->set_inp_ext, val);
807 }
808
809 cas->inp_val = val;
810}
811
812int cas_get_inp (const cassette_t *cas)
813{
814 return (cas->inp_val);
815}
816
817void cas_set_out (cassette_t *cas, int val)
818{
819 val = (val != 0);
820
821 if (cas->out_val == val) {
822 return;
823 }
824
825 if ((cas->run == 0) || (cas->record == 0)) {
826 cas->out_val = val;
827 cas->inp_val = val;
828 return;
829 }
830
831 cas_write_pulse (cas, cas->counter, cas->out_val ? 1 : -1);
832
833 cas->counter = 0;
834
835 cas->out_val = val;
836}
837
838void cas_clock (cassette_t *cas)
839{
840 int v;
841
842 if (cas->run == 0) {
843 return;
844 }
845
846 if (cas->motor_delay_count > 0) {
847 cas->motor_delay_count -= 1;
848
849 if (cas->motor_delay_count == 0) {
850 cas_run_stop (cas);
851 }
852 }
853
854 if (cas->record) {
855 cas->counter += 1;
856 return;
857 }
858
859 if (cas->counter > 1) {
860 cas->counter -= 1;
861 return;
862 }
863
864 if (cas_read_pulse (cas, &cas->counter, &v)) {
865 cas->eof = 1;
866 cas_press_keys (cas, 0, 0);
867 return;
868 }
869
870 cas_set_inp (cas, v >= 0);
871}