Files
86Box-fork/src/device/cassette.c

724 lines
13 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];
unsigned long cassette_pos, cassette_srate;
int cassette_enable;
int cassette_append, 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)
{
unsigned i;
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 (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, *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)
{
unsigned i;
val = (val != 0);
if (val == cas->motor) {
return;
}
if ((val == 0) && cas->save && cas->pcm) {
for (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 (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)
{
unsigned long i, 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 (i = 0; i < n; i++) {
pc_cas_write_smp (cas, cas->pcm_out_val);
}
}
else {
for (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(void *p)
{
if (cassette != NULL) {
free(cassette);
cassette = NULL;
}
}
static void
cassette_callback(void *p)
{
pc_cassette_t *cas = (pc_cassette_t *) p;
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(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 = {
"IBM PC/PCjr Cassette Device",
0,
0,
cassette_init, cassette_close, NULL,
{ NULL }, NULL, NULL,
NULL
};