Files
86Box/src/device/cassette.c
OBattler cd3f29f622 Part 2.
2024-08-27 03:29:37 +02:00

730 lines
14 KiB
C

/*****************************************************************************
* pce *
*****************************************************************************/
/*****************************************************************************
* File name: src/arch/ibmpc/cassette.c *
* Created: 2008-11-25 by Hampa Hug <hampa@hampa.ch> *
* Copyright: (C) 2008-2019 Hampa Hug <hampa@hampa.ch> *
*****************************************************************************/
/*****************************************************************************
* This program is free software. You can redistribute it and / or modify it *
* under the terms of the GNU General Public License version 2 as published *
* by the Free Software Foundation. *
* *
* 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. *
*****************************************************************************/
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/device.h>
#include "cpu.h"
#include <86box/machine.h>
#include <86box/plat.h>
#include <86box/ui.h>
#include <86box/timer.h>
#include <86box/pit.h>
#include <86box/cassette.h>
// #include <lib/console.h>
#define CAS_CLK 1193182
pc_cassette_t *cassette;
char cassette_fname[512];
char cassette_mode[512];
char * cassette_image_history[CASSETTE_IMAGE_HISTORY];
unsigned long cassette_pos;
unsigned long cassette_srate;
int cassette_enable;
int cassette_append;
int cassette_pcm;
int cassette_ui_writeprot;
static int cassette_cycles = -1;
static void pc_cas_reset(pc_cassette_t *cas);
#ifdef ENABLE_CASSETTE_LOG
int cassette_do_log = ENABLE_CASSETTE_LOG;
static void
cassette_log(const char *fmt, ...)
{
va_list ap;
if (cassette_do_log) {
va_start(ap, fmt);
pclog_ex(fmt, ap);
va_end(ap);
}
}
#else
# define cassette_log(fmt, ...)
#endif
void
pc_cas_init(pc_cassette_t *cas)
{
cas->save = 0;
cas->pcm = 0;
cas->motor = 0;
ui_sb_update_icon(SB_CASSETTE, 0);
cas->position = 0;
cas->position_save = 0;
cas->position_load = 0;
cas->data_out = 0;
cas->data_inp = 0;
cas->pcm_out_vol = 64;
cas->pcm_out_val = 0;
cas->cas_out_cnt = 0;
cas->cas_out_buf = 0;
cas->cas_inp_cnt = 0;
cas->cas_inp_buf = 0;
cas->cas_inp_bit = 0;
cas->clk = 0;
cas->clk_pcm = 0;
cas->clk_out = 0;
cas->clk_inp = 0;
cas->srate = 44100;
cas->close = 0;
cas->fname = NULL;
cas->fp = NULL;
pc_cas_reset(cas);
}
void
pc_cas_free(pc_cassette_t *cas)
{
free(cas->fname);
if (cas->close) {
fclose(cas->fp);
}
}
pc_cassette_t *
pc_cas_new(void)
{
pc_cassette_t *cas;
cas = malloc(sizeof(pc_cassette_t));
if (cas == NULL) {
return (NULL);
}
pc_cas_init(cas);
return cas;
}
void
pc_cas_del(pc_cassette_t *cas)
{
if (cas != NULL) {
pc_cas_free(cas);
free(cas);
}
}
int
pc_cas_set_fname(pc_cassette_t *cas, const char *fname)
{
unsigned n;
const char *ext;
if (cas->close)
fclose(cas->fp);
cas->close = 0;
cas->fp = NULL;
free(cas->fname);
cas->fname = NULL;
cas->position = 0;
cas->position_save = 0;
cas->position_load = 0;
if (fname == NULL) {
ui_sb_update_icon_state(SB_CASSETTE, 1);
return 0;
}
cas->fp = plat_fopen(fname, "r+b");
if (cas->fp == NULL)
cas->fp = plat_fopen(fname, "w+b");
if (cas->fp == NULL) {
ui_sb_update_icon_state(SB_CASSETTE, 1);
return 1;
}
cas->close = 1;
pc_cas_append(cas);
cas->position_save = cas->position;
if (cas->save == 0)
pc_cas_set_position(cas, 0);
n = strlen(fname);
cas->fname = malloc((n + 1) * sizeof(char));
if (cas->fname != NULL)
memcpy(cas->fname, fname, (n + 1) * sizeof(char));
if (n > 4) {
ext = fname + (n - 4);
/* Has to be 44.1 kHz, mono, 8-bit. */
if (stricmp(ext, ".pcm") == 0)
pc_cas_set_pcm(cas, 1);
else if (stricmp(ext, ".raw") == 0)
pc_cas_set_pcm(cas, 1);
else if (stricmp(ext, ".wav") == 0)
pc_cas_set_pcm(cas, 1);
else if (stricmp(ext, ".cas") == 0)
pc_cas_set_pcm(cas, 0);
}
return 0;
}
static void
pc_cas_reset(pc_cassette_t *cas)
{
cas->clk_pcm = 0;
cas->clk_out = cas->clk;
cas->clk_inp = 0;
cas->pcm_out_val = 0;
cas->cas_out_cnt = 0;
cas->cas_out_buf = 0;
cas->cas_inp_cnt = 0;
cas->cas_inp_buf = 0;
cas->cas_inp_bit = 0;
for (uint8_t i = 0; i < 3; i++) {
cas->pcm_inp_fir[i] = 0;
}
}
int
pc_cas_get_mode(const pc_cassette_t *cas)
{
return (cas->save);
}
void
pc_cas_set_mode(pc_cassette_t *cas, int save)
{
save = (save != 0);
if (cas->save == save) {
return;
}
if (cas->save) {
cas->position_save = cas->position;
cas->position = cas->position_load;
} else {
cas->position_load = cas->position;
cas->position = cas->position_save;
}
cas->save = save;
memset(cassette_mode, 0x00, sizeof(cassette_mode));
if (save)
memcpy(cassette_mode, "save", strlen("save") + 1);
else
memcpy(cassette_mode, "load", strlen("load") + 1);
if (cas->fp != NULL) {
fflush(cas->fp);
pc_cas_set_position(cas, cas->position);
}
pc_cas_reset(cas);
}
int
pc_cas_get_pcm(const pc_cassette_t *cas)
{
return (cas->pcm);
}
void
pc_cas_set_pcm(pc_cassette_t *cas, int pcm)
{
cas->pcm = (pcm != 0);
cassette_pcm = (pcm != 0);
pc_cas_reset(cas);
}
unsigned long
pc_cas_get_srate(const pc_cassette_t *cas)
{
return (cas->srate);
}
void
pc_cas_set_srate(pc_cassette_t *cas, unsigned long srate)
{
cas->srate = srate;
pc_cas_reset(cas);
}
void
pc_cas_rewind(pc_cassette_t *cas)
{
if (cas->fp != NULL) {
rewind(cas->fp);
cas->position = 0;
}
pc_cas_reset(cas);
}
void
pc_cas_append(pc_cassette_t *cas)
{
if (cas->fp != NULL) {
fseek(cas->fp, 0, SEEK_END);
cas->position = ftell(cas->fp);
}
pc_cas_reset(cas);
}
unsigned long
pc_cas_get_position(const pc_cassette_t *cas)
{
return (cas->position);
}
int
pc_cas_set_position(pc_cassette_t *cas, unsigned long pos)
{
if (cas->fp == NULL) {
return 1;
}
if (fseek(cas->fp, pos, SEEK_SET) != 0) {
return 1;
}
cas->position = pos;
pc_cas_reset(cas);
return 0;
}
static void
pc_cas_read_bit(pc_cassette_t *cas)
{
int val;
if (cas->cas_inp_cnt == 0) {
if (cas->fp == NULL) {
return;
}
if (feof(cas->fp)) {
return;
}
val = fgetc(cas->fp);
if (val == EOF) {
cassette_log("cassette EOF at %lu\n", cas->position);
return;
}
cas->position += 1;
cas->cas_inp_cnt = 8;
cas->cas_inp_buf = val;
}
cas->cas_inp_bit = ((cas->cas_inp_buf & 0x80) != 0);
cas->cas_inp_buf = (cas->cas_inp_buf << 1) & 0xff;
cas->cas_inp_cnt -= 1;
}
static int
pc_cas_read_smp(pc_cassette_t *cas)
{
int smp;
int *fir;
if (feof(cas->fp)) {
return 0;
}
smp = fgetc(cas->fp);
if (smp == EOF) {
cassette_log("cassette EOF at %lu\n", cas->position);
return 0;
}
cas->position += 1;
fir = cas->pcm_inp_fir;
fir[0] = fir[1];
fir[1] = fir[2];
fir[2] = (smp & 0x80) ? (smp - 256) : smp;
smp = (fir[0] + 2 * fir[1] + fir[2]) / 4;
return smp;
}
static void
pc_cas_write_bit(pc_cassette_t *cas, unsigned char val)
{
if (val && !cassette_ui_writeprot) {
cas->cas_out_buf |= (0x80 >> cas->cas_out_cnt);
}
cas->cas_out_cnt += 1;
if (cas->cas_out_cnt >= 8) {
if (cas->fp != NULL) {
if (!cassette_ui_writeprot)
fputc(cas->cas_out_buf, cas->fp);
cas->position += 1;
}
cas->cas_out_buf = 0;
cas->cas_out_cnt = 0;
}
}
static void
pc_cas_write_smp(pc_cassette_t *cas, int val)
{
unsigned char smp;
if (val < 0) {
smp = (val < -127) ? 0x80 : (val + 256);
} else {
smp = (val > 127) ? 0x7f : val;
}
if (!cassette_ui_writeprot)
fputc(smp, cas->fp);
cas->position += 1;
}
void
pc_cas_set_motor(pc_cassette_t *cas, unsigned char val)
{
val = (val != 0);
if (val == cas->motor) {
return;
}
if ((val == 0) && cas->save && cas->pcm) {
for (unsigned long i = 0; i < (cas->srate / 16); i++) {
pc_cas_write_smp(cas, 0);
}
}
cassette_log("cassette %S at %lu motor %s\n", (cas->fname != NULL) ? cas->fname : "<none>", cas->position, val ? "on" : "off");
cas->motor = val;
if (cas->fp != NULL) {
fflush(cas->fp);
pc_cas_set_position(cas, cas->position);
}
pc_cas_reset(cas);
if (cas->motor)
timer_set_delay_u64(&cas->timer, 8ULL * PITCONST);
else
timer_disable(&cas->timer);
ui_sb_update_icon(SB_CASSETTE, !!val);
}
unsigned char
pc_cas_get_inp(const pc_cassette_t *cas)
{
return (cas->data_inp);
}
void
pc_cas_set_out(pc_cassette_t *cas, unsigned char val)
{
unsigned long clk;
val = (val != 0);
if (cas->motor == 0) {
cas->data_inp = val;
return;
}
if (cas->data_out == val) {
return;
}
cas->data_out = val;
if (cas->pcm) {
cas->pcm_out_val = val ? -cas->pcm_out_vol : cas->pcm_out_vol;
return;
}
if (cas->save == 0) {
return;
}
if (val == 0) {
return;
}
clk = cas->clk - cas->clk_out;
cas->clk_out = cas->clk;
if (clk < (CAS_CLK / 4000)) {
;
} else if (clk < ((3 * CAS_CLK) / 4000)) {
pc_cas_write_bit(cas, 0);
} else if (clk < ((5 * CAS_CLK) / 4000)) {
pc_cas_write_bit(cas, 1);
}
}
void
pc_cas_print_state(UNUSED(const pc_cassette_t *cas))
{
cassette_log("%s %s %lu %s %lu\n", (cas->fname != NULL) ? cas->fname : "<none>", cas->pcm ? "pcm" : "cas", cas->srate, cas->save ? "save" : "load", cas->position);
}
static void
pc_cas_clock_pcm(pc_cassette_t *cas, unsigned long cnt)
{
uint64_t n;
int v = 0;
n = cas->srate * cnt + cas->clk_pcm;
cas->clk_pcm = n % CAS_CLK;
n = n / CAS_CLK;
if (n == 0) {
return;
}
if (cas->save) {
for (uint64_t i = 0; i < n; i++) {
pc_cas_write_smp(cas, cas->pcm_out_val);
}
} else {
for (uint64_t i = 0; i < n; i++) {
v = pc_cas_read_smp(cas);
}
cas->data_inp = (v < 0) ? 0 : 1;
}
}
void
pc_cas_clock(pc_cassette_t *cas, unsigned long cnt)
{
cas->clk += cnt;
if (cas->motor == 0) {
return;
}
if (cas->pcm) {
pc_cas_clock_pcm(cas, cnt);
return;
}
if (cas->save) {
return;
}
if (cas->clk_inp > cnt) {
cas->clk_inp -= cnt;
return;
}
cnt -= cas->clk_inp;
cas->data_inp = !cas->data_inp;
if (cas->data_inp) {
pc_cas_read_bit(cas);
}
if (cas->cas_inp_bit) {
cas->clk_inp = CAS_CLK / 2000;
} else {
cas->clk_inp = CAS_CLK / 4000;
}
if (cas->clk_inp > cnt) {
cas->clk_inp -= cnt;
}
}
void
pc_cas_advance(pc_cassette_t *cas)
{
int ticks;
cpu_s = (CPU *) &cpu_f->cpus[cpu_effective];
if (cas->motor == 0)
return;
if (cassette_cycles == -1)
cassette_cycles = cycles;
if (cycles <= cassette_cycles)
ticks = (cassette_cycles - cycles);
else
ticks = (cassette_cycles + (cpu_s->rspeed / 100) - cycles);
cassette_cycles = cycles;
pc_cas_clock(cas, ticks);
}
static void
cassette_close(UNUSED(void *priv))
{
if (cassette != NULL) {
free(cassette);
cassette = NULL;
}
}
static void
cassette_callback(void *priv)
{
pc_cassette_t *cas = (pc_cassette_t *) priv;
pc_cas_clock(cas, 8);
if (cas->motor)
ui_sb_update_icon(SB_CASSETTE, 1);
timer_advance_u64(&cas->timer, 8ULL * PITCONST);
}
static void *
cassette_init(UNUSED(const device_t *info))
{
cassette = NULL;
if (cassette_pcm == 1)
cassette_pcm = -1;
cassette_log("CASSETTE: file=%s mode=%s pcm=%d srate=%lu pos=%lu append=%d\n",
(cassette_fname != NULL) ? cassette_fname : "<none>", cassette_mode, cassette_pcm, cassette_srate, cassette_pos, cassette_append);
cassette = pc_cas_new();
if (cassette == NULL) {
cassette_log("ERROR: *** alloc failed\n");
return NULL;
}
if (strlen(cassette_fname) == 0) {
if (pc_cas_set_fname(cassette, NULL)) {
cassette_log("ERROR: *** opening file failed (%s)\n", cassette_fname);
}
} else {
if (pc_cas_set_fname(cassette, cassette_fname)) {
cassette_log("ERROR: *** opening file failed (%s)\n", cassette_fname);
}
}
if (strcmp(cassette_mode, "load") == 0)
pc_cas_set_mode(cassette, 0);
else if (strcmp(cassette_mode, "save") == 0)
pc_cas_set_mode(cassette, 1);
else {
cassette_log("ERROR: *** unknown cassette mode (%s)\n", cassette_mode);
}
if (cassette_append)
pc_cas_append(cassette);
else
pc_cas_set_position(cassette, cassette_pos);
if (cassette_pcm >= 0)
pc_cas_set_pcm(cassette, cassette_pcm);
pc_cas_set_srate(cassette, cassette_srate);
timer_add(&cassette->timer, cassette_callback, cassette, 0);
return cassette;
}
const device_t cassette_device = {
.name = "IBM PC/PCjr Cassette Device",
.internal_name = "cassette",
.flags = 0,
.local = 0,
.init = cassette_init,
.close = cassette_close,
.reset = NULL,
{ .available = NULL },
.speed_changed = NULL,
.force_redraw = NULL,
.config = NULL
};