diff options
author | Wim Taymans <wim.taymans@gmail.com> | 2006-07-13 15:07:28 +0000 |
---|---|---|
committer | Wim Taymans <wim.taymans@gmail.com> | 2006-07-13 15:07:28 +0000 |
commit | aae22fa1c92df5ee70fa478f831c64287344879c (patch) | |
tree | 82d1b83aff375d2ab6b0d09427560550c1b7db1e | |
parent | 9d2c04267dec2eda43a150b2541d5c5a0f9eaf18 (diff) | |
download | gst-plugins-bad-aae22fa1c92df5ee70fa478f831c64287344879c.tar.gz gst-plugins-bad-aae22fa1c92df5ee70fa478f831c64287344879c.tar.bz2 gst-plugins-bad-aae22fa1c92df5ee70fa478f831c64287344879c.zip |
Added NSF decoder plugin. Fixes 151192.
Original commit message from CVS:
Based on patches by: Johan Dahlin <johan at gnome dot org>
Ronald Bultje <rbultje at ronald dot bitfreak dot net>
* configure.ac:
* gst/nsf/Makefile.am:
* gst/nsf/dis6502.h:
* gst/nsf/fds_snd.c:
* gst/nsf/fds_snd.h:
* gst/nsf/fmopl.c:
* gst/nsf/fmopl.h:
* gst/nsf/gstnsf.c:
* gst/nsf/gstnsf.h:
* gst/nsf/log.c:
* gst/nsf/log.h:
* gst/nsf/memguard.c:
* gst/nsf/memguard.h:
* gst/nsf/mmc5_snd.c:
* gst/nsf/mmc5_snd.h:
* gst/nsf/nes6502.c:
* gst/nsf/nes6502.h:
* gst/nsf/nes_apu.c:
* gst/nsf/nes_apu.h:
* gst/nsf/nsf.c:
* gst/nsf/nsf.h:
* gst/nsf/osd.h:
* gst/nsf/types.h:
* gst/nsf/vrc7_snd.c:
* gst/nsf/vrc7_snd.h:
* gst/nsf/vrcvisnd.c:
* gst/nsf/vrcvisnd.h:
Added NSF decoder plugin. Fixes 151192.
-rw-r--r-- | ChangeLog | 34 | ||||
m--------- | common | 0 | ||||
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | gst/nsf/Makefile.am | 36 | ||||
-rw-r--r-- | gst/nsf/dis6502.h | 79 | ||||
-rw-r--r-- | gst/nsf/fds_snd.c | 124 | ||||
-rw-r--r-- | gst/nsf/fds_snd.h | 77 | ||||
-rw-r--r-- | gst/nsf/fmopl.c | 1392 | ||||
-rw-r--r-- | gst/nsf/fmopl.h | 164 | ||||
-rw-r--r-- | gst/nsf/gstnsf.c | 635 | ||||
-rw-r--r-- | gst/nsf/gstnsf.h | 86 | ||||
-rw-r--r-- | gst/nsf/log.c | 148 | ||||
-rw-r--r-- | gst/nsf/log.h | 76 | ||||
-rw-r--r-- | gst/nsf/memguard.c | 398 | ||||
-rw-r--r-- | gst/nsf/memguard.h | 95 | ||||
-rw-r--r-- | gst/nsf/mmc5_snd.c | 383 | ||||
-rw-r--r-- | gst/nsf/mmc5_snd.h | 109 | ||||
-rw-r--r-- | gst/nsf/nes6502.c | 2438 | ||||
-rw-r--r-- | gst/nsf/nes6502.h | 160 | ||||
-rw-r--r-- | gst/nsf/nes_apu.c | 1246 | ||||
-rw-r--r-- | gst/nsf/nes_apu.h | 354 | ||||
-rw-r--r-- | gst/nsf/nsf.c | 648 | ||||
-rw-r--r-- | gst/nsf/nsf.h | 173 | ||||
-rw-r--r-- | gst/nsf/osd.h | 123 | ||||
-rw-r--r-- | gst/nsf/types.h | 125 | ||||
-rw-r--r-- | gst/nsf/vrc7_snd.c | 376 | ||||
-rw-r--r-- | gst/nsf/vrc7_snd.h | 99 | ||||
-rw-r--r-- | gst/nsf/vrcvisnd.c | 241 | ||||
-rw-r--r-- | gst/nsf/vrcvisnd.h | 112 |
29 files changed, 9933 insertions, 0 deletions
@@ -1,3 +1,37 @@ +2006-07-13 Wim Taymans <wim@fluendo.com> + + Based on patches by: Johan Dahlin <johan at gnome dot org> + Ronald Bultje <rbultje at ronald dot bitfreak dot net> + + * configure.ac: + * gst/nsf/Makefile.am: + * gst/nsf/dis6502.h: + * gst/nsf/fds_snd.c: + * gst/nsf/fds_snd.h: + * gst/nsf/fmopl.c: + * gst/nsf/fmopl.h: + * gst/nsf/gstnsf.c: + * gst/nsf/gstnsf.h: + * gst/nsf/log.c: + * gst/nsf/log.h: + * gst/nsf/memguard.c: + * gst/nsf/memguard.h: + * gst/nsf/mmc5_snd.c: + * gst/nsf/mmc5_snd.h: + * gst/nsf/nes6502.c: + * gst/nsf/nes6502.h: + * gst/nsf/nes_apu.c: + * gst/nsf/nes_apu.h: + * gst/nsf/nsf.c: + * gst/nsf/nsf.h: + * gst/nsf/osd.h: + * gst/nsf/types.h: + * gst/nsf/vrc7_snd.c: + * gst/nsf/vrc7_snd.h: + * gst/nsf/vrcvisnd.c: + * gst/nsf/vrcvisnd.h: + Added NSF decoder plugin. Fixes 151192. + 2006-07-13 Tim-Philipp Müller <tim at centricular dot net> * tests/check/Makefile.am: diff --git a/common b/common -Subproject dd173e2720ac21e4a47c97705d7ff32271a0ee6 +Subproject 53ecdc0c97a2992e5abeddd41d514bc142401e5 diff --git a/configure.ac b/configure.ac index cbb09f50..ce0660b2 100644 --- a/configure.ac +++ b/configure.ac @@ -82,6 +82,7 @@ GST_PLUGINS_ALL="\ gdp \ h264parse \ modplug \ + nsf \ spectrum \ speed \ qtdemux \ @@ -822,6 +823,7 @@ gst/gdp/Makefile gst/h264parse/Makefile gst/modplug/Makefile gst/modplug/libmodplug/Makefile +gst/nsf/Makefile gst/spectrum/Makefile gst/speed/Makefile gst/qtdemux/Makefile diff --git a/gst/nsf/Makefile.am b/gst/nsf/Makefile.am new file mode 100644 index 00000000..bff272a2 --- /dev/null +++ b/gst/nsf/Makefile.am @@ -0,0 +1,36 @@ + +plugin_LTLIBRARIES = libgstnsf.la + +NOSEFART_SOURCES=fmopl.c \ + log.c \ + mmc5_snd.c \ + nes_apu.c \ + vrc7_snd.c \ + fds_snd.c \ + memguard.c \ + nes6502.c \ + nsf.c \ + vrcvisnd.c + +NOSEFART_INCLUDES=fmopl.h \ + log.h \ + mmc5_snd.h \ + nes_apu.h \ + osd.h \ + vrc7_snd.h \ + fds_snd.h \ + memguard.h \ + nes6502.h \ + nsf.h \ + types.h \ + vrcvisnd.h + +libgstnsf_la_SOURCES = gstnsf.c $(NOSEFART_SOURCES) + +libgstnsf_la_CFLAGS = $(GST_CFLAGS) -DNSF_PLAYER +libgstnsf_la_LIBADD = +libgstnsf_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) + +noinst_HEADERS = gstnsf.h $(NOSEFART_INCLUDES) + +EXTRA_DIST = diff --git a/gst/nsf/dis6502.h b/gst/nsf/dis6502.h new file mode 100644 index 00000000..578defe6 --- /dev/null +++ b/gst/nsf/dis6502.h @@ -0,0 +1,79 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** dis6502.h +** +** 6502 disassembler header +** $Id$ +*/ + +#ifndef _DIS6502_H_ +#define _DIS6502_H_ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +extern void nes6502_disasm(uint32 PC, uint8 P, uint8 A, uint8 X, uint8 Y, uint8 S); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* !_DIS6502_H_ */ + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.4 2000/06/09 15:12:25 matt +** initial revision +** +*/ + diff --git a/gst/nsf/fds_snd.c b/gst/nsf/fds_snd.c new file mode 100644 index 00000000..d2cfccb7 --- /dev/null +++ b/gst/nsf/fds_snd.c @@ -0,0 +1,124 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** fds_snd.c +** +** Famicom Disk System sound emulation +** $Id$ +*/ + +#include "types.h" +#include "nes_apu.h" +#include "fds_snd.h" + +static int32 fds_incsize = 0; + +/* mix sound channels together */ +static int32 +fds_process (void) +{ + int32 output; + + output = 0; + + return output; +} + +/* write to registers */ +static void +fds_write (uint32 address, uint8 value) +{ +} + +/* reset state of vrcvi sound channels */ +static void +fds_reset (void) +{ + fds_incsize = apu_getcyclerate (); +} + +static void +fds_init (void) +{ +} + +/* TODO: bleh */ +static void +fds_shutdown (void) +{ +} + +static apu_memwrite fds_memwrite[] = { + {0x4040, 0x4092, fds_write}, + {-1, -1, NULL} +}; + +apuext_t fds_ext = { + fds_init, + fds_shutdown, + fds_reset, + fds_process, + NULL, /* no reads */ + fds_memwrite +}; + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.3 2000/07/03 02:18:53 matt +** much better external module exporting +** +** Revision 1.2 2000/06/20 04:06:16 matt +** migrated external sound definition to apu module +** +** Revision 1.1 2000/06/20 00:06:47 matt +** initial revision +** +*/ diff --git a/gst/nsf/fds_snd.h b/gst/nsf/fds_snd.h new file mode 100644 index 00000000..4ec90f15 --- /dev/null +++ b/gst/nsf/fds_snd.h @@ -0,0 +1,77 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** fds_snd.h +** +** Famicom Disk System sound emulation +** $Id$ +*/ + +#ifndef _FDS_SND_H_ +#define _FDS_SND_H_ + +#include "nes_apu.h" + +extern apuext_t fds_ext; + + +#endif /* _VRCVISND_H_ */ + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.2 2000/06/20 04:06:16 matt +** migrated external sound definition to apu module +** +** Revision 1.1 2000/06/20 00:06:47 matt +** initial revision +** +*/ + diff --git a/gst/nsf/fmopl.c b/gst/nsf/fmopl.c new file mode 100644 index 00000000..c1e321f3 --- /dev/null +++ b/gst/nsf/fmopl.c @@ -0,0 +1,1392 @@ +/* +** +** File: fmopl.c -- software implementation of FM sound generator +** +** Copyright (C) 1999 Tatsuyuki Satoh , MultiArcadeMachineEmurator development +** +** Version 0.36f +** +*/ + +/* + preliminary : + Problem : + note: +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <math.h> + /* #include "driver.h" *//* use M.A.M.E. */ +#include "fmopl.h" + +/* MPC - hacks */ +#include "types.h" +#include "log.h" + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +/* -------------------- preliminary define section --------------------- */ +/* attack/decay rate time rate */ +#define OPL_ARRATE 141280 /* RATE 4 = 2826.24ms @ 3.6MHz */ +#define OPL_DRRATE 1956000 /* RATE 4 = 39280.64ms @ 3.6MHz */ + +#define DELTAT_MIXING_LEVEL (1) /* DELTA-T ADPCM MIXING LEVEL */ + +#define FREQ_BITS 24 /* frequency turn */ + +/* counter bits = 20 , octerve 7 */ +#define FREQ_RATE (1<<(FREQ_BITS-20)) +#define TL_BITS (FREQ_BITS+2) + +/* final output shift , limit minimum and maximum */ +#define OPL_OUTSB (TL_BITS+3-16) /* OPL output final shift 16bit */ +#define OPL_MAXOUT (0x7fff<<OPL_OUTSB) +#define OPL_MINOUT (-0x8000<<OPL_OUTSB) + +/* -------------------- quality selection --------------------- */ + +/* sinwave entries */ +/* used static memory = SIN_ENT * 4 (byte) */ +#define SIN_ENT 2048 + +/* output level entries (envelope,sinwave) */ +/* envelope counter lower bits */ +#define ENV_BITS 16 +/* envelope output entries */ +#define EG_ENT 4096 +/* used dynamic memory = EG_ENT*4*4(byte)or EG_ENT*6*4(byte) */ +/* used static memory = EG_ENT*4 (byte) */ + +#define EG_OFF ((2*EG_ENT)<<ENV_BITS) /* OFF */ +#define EG_DED EG_OFF +#define EG_DST (EG_ENT<<ENV_BITS) /* DECAY START */ +#define EG_AED EG_DST +#define EG_AST 0 /* ATTACK START */ + +#define EG_STEP (96.0/EG_ENT) /* OPL is 0.1875 dB step */ + +/* LFO table entries */ +#define VIB_ENT 512 +#define VIB_SHIFT (32-9) +#define AMS_ENT 512 +#define AMS_SHIFT (32-9) + +#define VIB_RATE 256 + +/* -------------------- local defines , macros --------------------- */ + +/* register number to channel number , slot offset */ +#define SLOT1 0 +#define SLOT2 1 + +/* envelope phase */ +#define ENV_MOD_RR 0x00 +#define ENV_MOD_DR 0x01 +#define ENV_MOD_AR 0x02 + +/* -------------------- tables --------------------- */ +static const int slot_array[32] = { + 0, 2, 4, 1, 3, 5, -1, -1, + 6, 8, 10, 7, 9, 11, -1, -1, + 12, 14, 16, 13, 15, 17, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 +}; + +/* key scale level */ +#define ML(x) ((UINT32)((x)*0.1875*2/EG_STEP)) +static const UINT32 KSL_TABLE[8 * 16] = { + /* OCT 0 */ + ML (0.000), ML (0.000), ML (0.000), ML (0.000), + ML (0.000), ML (0.000), ML (0.000), ML (0.000), + ML (0.000), ML (0.000), ML (0.000), ML (0.000), + ML (0.000), ML (0.000), ML (0.000), ML (0.000), + /* OCT 1 */ + ML (0.000), ML (0.000), ML (0.000), ML (0.000), + ML (0.000), ML (0.000), ML (0.000), ML (0.000), + ML (0.000), ML (0.750), ML (1.125), ML (1.500), + ML (1.875), ML (2.250), ML (2.625), ML (3.000), + /* OCT 2 */ + ML (0.000), ML (0.000), ML (0.000), ML (0.000), + ML (0.000), ML (1.125), ML (1.875), ML (2.625), + ML (3.000), ML (3.750), ML (4.125), ML (4.500), + ML (4.875), ML (5.250), ML (5.625), ML (6.000), + /* OCT 3 */ + ML (0.000), ML (0.000), ML (0.000), ML (1.875), + ML (3.000), ML (4.125), ML (4.875), ML (5.625), + ML (6.000), ML (6.750), ML (7.125), ML (7.500), + ML (7.875), ML (8.250), ML (8.625), ML (9.000), + /* OCT 4 */ + ML (0.000), ML (0.000), ML (3.000), ML (4.875), + ML (6.000), ML (7.125), ML (7.875), ML (8.625), + ML (9.000), ML (9.750), ML (10.125), ML (10.500), + ML (10.875), ML (11.250), ML (11.625), ML (12.000), + /* OCT 5 */ + ML (0.000), ML (3.000), ML (6.000), ML (7.875), + ML (9.000), ML (10.125), ML (10.875), ML (11.625), + ML (12.000), ML (12.750), ML (13.125), ML (13.500), + ML (13.875), ML (14.250), ML (14.625), ML (15.000), + /* OCT 6 */ + ML (0.000), ML (6.000), ML (9.000), ML (10.875), + ML (12.000), ML (13.125), ML (13.875), ML (14.625), + ML (15.000), ML (15.750), ML (16.125), ML (16.500), + ML (16.875), ML (17.250), ML (17.625), ML (18.000), + /* OCT 7 */ + ML (0.000), ML (9.000), ML (12.000), ML (13.875), + ML (15.000), ML (16.125), ML (16.875), ML (17.625), + ML (18.000), ML (18.750), ML (19.125), ML (19.500), + ML (19.875), ML (20.250), ML (20.625), ML (21.000) +}; + +#undef ML + +/* sustain lebel table (3db per step) */ +/* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/ +#define SC(db) ((INT32) (db*((3/EG_STEP)*(1<<ENV_BITS)))+EG_DST) +static const INT32 SL_TABLE[16] = { + SC (0), SC (1), SC (2), SC (3), SC (4), SC (5), SC (6), SC (7), + SC (8), SC (9), SC (10), SC (11), SC (12), SC (13), SC (14), SC (31) +}; + +#undef SC + +#define TL_MAX (EG_ENT*2) /* limit(tl + ksr + envelope) + sinwave */ +/* TotalLevel : 48 24 12 6 3 1.5 0.75 (dB) */ +/* TL.TABLE[ 0 to TL_MAX ] : plus section */ +/* TL.TABLE[ TL_MAX to TL_MAX+TL_MAX-1 ] : minus section */ +static union +{ + INT32 *TABLE; + void *TABLE_PTR; +} TL; + +/* pointers to TL.TABLE with sinwave output offset */ +static union +{ + INT32 **TABLE; + void *TABLE_PTR; +} SIN; + +/* LFO table */ +static union +{ + INT32 *TABLE; + void *TABLE_PTR; +} AMS; + +static union +{ + INT32 *TABLE; + void *TABLE_PTR; +} VIB; + +/* envelope output curve table */ +/* attack + decay + OFF */ +static INT32 ENV_CURVE[2 * EG_ENT + 1]; + +/* multiple table */ +#define ML(x) ((UINT32) (2*(x))) +static const UINT32 MUL_TABLE[16] = { +/* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 */ + ML (0.50), ML (1.00), ML (2.00), ML (3.00), ML (4.00), ML (5.00), ML (6.00), + ML (7.00), + ML (8.00), ML (9.00), ML (10.00), ML (10.00), ML (12.00), ML (12.00), + ML (15.00), ML (15.00) +}; + +#undef ML + +/* dummy attack / decay rate ( when rate == 0 ) */ +static INT32 RATE_0[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + +/* -------------------- static state --------------------- */ + +/* lock level of common table */ +static int num_lock = 0; + +/* work table */ +static void *cur_chip = NULL; /* current chip point */ + +/* currenct chip state */ +/* static FMSAMPLE *bufL,*bufR; */ +static OPL_CH *S_CH; +static OPL_CH *E_CH; +OPL_SLOT *SLOT7_1, *SLOT7_2, *SLOT8_1, *SLOT8_2; + +static INT32 outd[1]; +static INT32 ams; +static INT32 vib; +INT32 *ams_table; +INT32 *vib_table; +static INT32 amsIncr; +static INT32 vibIncr; +static INT32 feedback2; /* connect for SLOT 2 */ + +/* log output level */ +#define LOG_ERR 3 /* ERROR */ +#define LOG_WAR 2 /* WARNING */ +#define LOG_INF 1 /* INFORMATION */ + +#define LOG_LEVEL LOG_INF + +/* #define LOG(n,x) if( (n)>=LOG_LEVEL ) logerror x */ +#define LOG(n,x) if( (n)>=LOG_LEVEL ) log_printf x + +/* --------------------- subroutines --------------------- */ + +INLINE int +Limit (int val, int max, int min) +{ + if (val > max) + val = max; + else if (val < min) + val = min; + + return val; +} + +/* status set and IRQ handling */ +INLINE void +OPL_STATUS_SET (FM_OPL * OPL, int flag) +{ + /* set status flag */ + OPL->status |= flag; + if (!(OPL->status & 0x80)) { + if (OPL->status & OPL->statusmask) { /* IRQ on */ + OPL->status |= 0x80; + /* callback user interrupt handler (IRQ is OFF to ON) */ + if (OPL->IRQHandler) + (OPL->IRQHandler) (OPL->IRQParam, 1); + } + } +} + +/* status reset and IRQ handling */ +INLINE void +OPL_STATUS_RESET (FM_OPL * OPL, int flag) +{ + /* reset status flag */ + OPL->status &= ~flag; + if ((OPL->status & 0x80)) { + if (!(OPL->status & OPL->statusmask)) { + OPL->status &= 0x7f; + /* callback user interrupt handler (IRQ is ON to OFF) */ + if (OPL->IRQHandler) + (OPL->IRQHandler) (OPL->IRQParam, 0); + } + } +} + +/* IRQ mask set */ +INLINE void +OPL_STATUSMASK_SET (FM_OPL * OPL, int flag) +{ + OPL->statusmask = flag; + /* IRQ handling check */ + OPL_STATUS_SET (OPL, 0); + OPL_STATUS_RESET (OPL, 0); +} + +/* ----- key on ----- */ +INLINE void +OPL_KEYON (OPL_SLOT * SLOT) +{ + /* sin wave restart */ + SLOT->Cnt = 0; + /* set attack */ + SLOT->evm = ENV_MOD_AR; + SLOT->evs = SLOT->evsa; + SLOT->evc = EG_AST; + SLOT->eve = EG_AED; +} + +/* ----- key off ----- */ +INLINE void +OPL_KEYOFF (OPL_SLOT * SLOT) +{ + if (SLOT->evm > ENV_MOD_RR) { + /* set envelope counter from envleope output */ + SLOT->evm = ENV_MOD_RR; + if (!(SLOT->evc & EG_DST)) + /* SLOT->evc = (ENV_CURVE[SLOT->evc>>ENV_BITS]<<ENV_BITS) + EG_DST; */ + SLOT->evc = EG_DST; + SLOT->eve = EG_DED; + SLOT->evs = SLOT->evsr; + } +} + +/* ---------- calcrate Envelope Generator & Phase Generator ---------- */ +/* return : envelope output */ +INLINE UINT32 +OPL_CALC_SLOT (OPL_SLOT * SLOT) +{ + /* calcrate envelope generator */ + if ((SLOT->evc += SLOT->evs) >= SLOT->eve) { + switch (SLOT->evm) { + case ENV_MOD_AR: /* ATTACK -> DECAY1 */ + /* next DR */ + SLOT->evm = ENV_MOD_DR; + SLOT->evc = EG_DST; + SLOT->eve = SLOT->SL; + SLOT->evs = SLOT->evsd; + break; + case ENV_MOD_DR: /* DECAY -> SL or RR */ + SLOT->evc = SLOT->SL; + SLOT->eve = EG_DED; + if (SLOT->eg_typ) { + SLOT->evs = 0; + } else { + SLOT->evm = ENV_MOD_RR; + SLOT->evs = SLOT->evsr; + } + break; + case ENV_MOD_RR: /* RR -> OFF */ + SLOT->evc = EG_OFF; + SLOT->eve = EG_OFF + 1; + SLOT->evs = 0; + break; + } + } + /* calcrate envelope */ + return SLOT->TLL + ENV_CURVE[SLOT->evc >> ENV_BITS] + (SLOT->ams ? ams : 0); +} + +/* set algorythm connection */ +static void +set_algorythm (OPL_CH * CH) +{ + INT32 *carrier = &outd[0]; + + CH->connect1 = CH->CON ? carrier : &feedback2; + CH->connect2 = carrier; +} + +/* ---------- frequency counter for operater update ---------- */ +INLINE void +CALC_FCSLOT (OPL_CH * CH, OPL_SLOT * SLOT) +{ + int ksr; + + /* frequency step counter */ + SLOT->Incr = CH->fc * SLOT->mul; + ksr = CH->kcode >> SLOT->KSR; + + if (SLOT->ksr != ksr) { + SLOT->ksr = ksr; + /* attack , decay rate recalcration */ + SLOT->evsa = SLOT->AR[ksr]; + SLOT->evsd = SLOT->DR[ksr]; + SLOT->evsr = SLOT->RR[ksr]; + } + SLOT->TLL = SLOT->TL + (CH->ksl_base >> SLOT->ksl); +} + +/* set multi,am,vib,EG-TYP,KSR,mul */ +INLINE void +set_mul (FM_OPL * OPL, int slot, int v) +{ + OPL_CH *CH = &OPL->P_CH[slot / 2]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + + SLOT->mul = MUL_TABLE[v & 0x0f]; + SLOT->KSR = (v & 0x10) ? 0 : 2; + SLOT->eg_typ = (v & 0x20) >> 5; + SLOT->vib = (v & 0x40); + SLOT->ams = (v & 0x80); + CALC_FCSLOT (CH, SLOT); +} + +/* set ksl & tl */ +INLINE void +set_ksl_tl (FM_OPL * OPL, int slot, int v) +{ + OPL_CH *CH = &OPL->P_CH[slot / 2]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + int ksl = v >> 6; /* 0 / 1.5 / 3 / 6 db/OCT */ + + SLOT->ksl = ksl ? 3 - ksl : 31; + SLOT->TL = (INT32) (((v & 0x3f) * (0.75 / EG_STEP))); /* 0.75db step */ + + if (!(OPL->mode & 0x80)) { /* not CSM latch total level */ + SLOT->TLL = SLOT->TL + (CH->ksl_base >> SLOT->ksl); + } +} + +/* set attack rate & decay rate */ +INLINE void +set_ar_dr (FM_OPL * OPL, int slot, int v) +{ + OPL_CH *CH = &OPL->P_CH[slot / 2]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + int ar = v >> 4; + int dr = v & 0x0f; + + SLOT->AR = ar ? &OPL->AR_TABLE[ar << 2] : RATE_0; + SLOT->evsa = SLOT->AR[SLOT->ksr]; + if (SLOT->evm == ENV_MOD_AR) + SLOT->evs = SLOT->evsa; + + SLOT->DR = dr ? &OPL->DR_TABLE[dr << 2] : RATE_0; + SLOT->evsd = SLOT->DR[SLOT->ksr]; + if (SLOT->evm == ENV_MOD_DR) + SLOT->evs = SLOT->evsd; +} + +/* set sustain level & release rate */ +INLINE void +set_sl_rr (FM_OPL * OPL, int slot, int v) +{ + OPL_CH *CH = &OPL->P_CH[slot / 2]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + int sl = v >> 4; + int rr = v & 0x0f; + + SLOT->SL = SL_TABLE[sl]; + if (SLOT->evm == ENV_MOD_DR) + SLOT->eve = SLOT->SL; + SLOT->RR = &OPL->DR_TABLE[rr << 2]; + SLOT->evsr = SLOT->RR[SLOT->ksr]; + if (SLOT->evm == ENV_MOD_RR) + SLOT->evs = SLOT->evsr; +} + +/* operator output calcrator */ +#define OP_OUT(slot,env,con) slot->wavetable[((slot->Cnt+con)/(0x1000000/SIN_ENT))&(SIN_ENT-1)][env] +/* ---------- calcrate one of channel ---------- */ +INLINE void +OPL_CALC_CH (OPL_CH * CH) +{ + UINT32 env_out; + OPL_SLOT *SLOT; + + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH->SLOT[SLOT1]; + env_out = OPL_CALC_SLOT (SLOT); + if (env_out < EG_ENT - 1) { + /* PG */ + if (SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib / VIB_RATE); + else + SLOT->Cnt += SLOT->Incr; + /* connectoion */ + if (CH->FB) { + int feedback1 = (CH->op1_out[0] + CH->op1_out[1]) >> CH->FB; + + CH->op1_out[1] = CH->op1_out[0]; + *CH->connect1 += CH->op1_out[0] = OP_OUT (SLOT, env_out, feedback1); + } else { + *CH->connect1 += OP_OUT (SLOT, env_out, 0); + } + } else { + CH->op1_out[1] = CH->op1_out[0]; + CH->op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH->SLOT[SLOT2]; + env_out = OPL_CALC_SLOT (SLOT); + if (env_out < EG_ENT - 1) { + /* PG */ + if (SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib / VIB_RATE); + else + SLOT->Cnt += SLOT->Incr; + /* connectoion */ + outd[0] += OP_OUT (SLOT, env_out, feedback2); + } +} + +/* ---------- calcrate rythm block ---------- */ +#define WHITE_NOISE_db 6.0 +INLINE void +OPL_CALC_RH (OPL_CH * CH) +{ + UINT32 env_tam, env_sd, env_top, env_hh; + int whitenoise = (rand () & 1) * ((int) (WHITE_NOISE_db / EG_STEP)); + INT32 tone8; + + OPL_SLOT *SLOT; + int env_out; + + /* BD : same as FM serial mode and output level is large */ + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH[6].SLOT[SLOT1]; + env_out = OPL_CALC_SLOT (SLOT); + if (env_out < EG_ENT - 1) { + /* PG */ + if (SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib / VIB_RATE); + else + SLOT->Cnt += SLOT->Incr; + /* connectoion */ + if (CH[6].FB) { + int feedback1 = (CH[6].op1_out[0] + CH[6].op1_out[1]) >> CH[6].FB; + + CH[6].op1_out[1] = CH[6].op1_out[0]; + feedback2 = CH[6].op1_out[0] = OP_OUT (SLOT, env_out, feedback1); + } else { + feedback2 = OP_OUT (SLOT, env_out, 0); + } + } else { + feedback2 = 0; + CH[6].op1_out[1] = CH[6].op1_out[0]; + CH[6].op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH[6].SLOT[SLOT2]; + env_out = OPL_CALC_SLOT (SLOT); + if (env_out < EG_ENT - 1) { + /* PG */ + if (SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib / VIB_RATE); + else + SLOT->Cnt += SLOT->Incr; + /* connectoion */ + outd[0] += OP_OUT (SLOT, env_out, feedback2) * 2; + } + + /* SD (17) = mul14[fnum7] + white noise + * TAM (15) = mul15[fnum8] + * TOP (18) = fnum6(mul18[fnum8]+whitenoise) + * HH (14) = fnum7(mul18[fnum8]+whitenoise) + white noise + */ + env_sd = OPL_CALC_SLOT (SLOT7_2) + whitenoise; + env_tam = OPL_CALC_SLOT (SLOT8_1); + env_top = OPL_CALC_SLOT (SLOT8_2); + env_hh = OPL_CALC_SLOT (SLOT7_1) + whitenoise; + + /* PG */ + if (SLOT7_1->vib) + SLOT7_1->Cnt += (2 * SLOT7_1->Incr * vib / VIB_RATE); + else + SLOT7_1->Cnt += 2 * SLOT7_1->Incr; + if (SLOT7_2->vib) + SLOT7_2->Cnt += ((CH[7].fc * 8) * vib / VIB_RATE); + else + SLOT7_2->Cnt += (CH[7].fc * 8); + if (SLOT8_1->vib) + SLOT8_1->Cnt += (SLOT8_1->Incr * vib / VIB_RATE); + else + SLOT8_1->Cnt += SLOT8_1->Incr; + if (SLOT8_2->vib) + SLOT8_2->Cnt += ((CH[8].fc * 48) * vib / VIB_RATE); + else + SLOT8_2->Cnt += (CH[8].fc * 48); + + tone8 = OP_OUT (SLOT8_2, whitenoise, 0); + + /* SD */ + if (env_sd < EG_ENT - 1) + outd[0] += OP_OUT (SLOT7_1, env_sd, 0) * 8; + /* TAM */ + if (env_tam < EG_ENT - 1) + outd[0] += OP_OUT (SLOT8_1, env_tam, 0) * 2; + /* TOP-CY */ + if (env_top < EG_ENT - 1) + outd[0] += OP_OUT (SLOT7_2, env_top, tone8) * 2; + /* HH */ + if (env_hh < EG_ENT - 1) + outd[0] += OP_OUT (SLOT7_2, env_hh, tone8) * 2; +} + +/* ----------- initialize time tabls ----------- */ +static void +init_timetables (FM_OPL * OPL, int ARRATE, int DRRATE) +{ + int i; + double rate; + + /* make attack rate & decay rate tables */ + for (i = 0; i < 4; i++) + OPL->AR_TABLE[i] = OPL->DR_TABLE[i] = 0; + for (i = 4; i <= 60; i++) { + rate = OPL->freqbase; /* frequency rate */ + if (i < 60) + rate *= 1.0 + (i & 3) * 0.25; /* b0-1 : x1 , x1.25 , x1.5 , x1.75 */ + rate *= 1 << ((i >> 2) - 1); /* b2-5 : shift bit */ + rate *= (double) (EG_ENT << ENV_BITS); + OPL->AR_TABLE[i] = (INT32) (rate / ARRATE); + OPL->DR_TABLE[i] = (INT32) (rate / DRRATE); + } + for (i = 60; i < 76; i++) { + OPL->AR_TABLE[i] = EG_AED - 1; + OPL->DR_TABLE[i] = OPL->DR_TABLE[60]; + } +#if 0 + for (i = 0; i < 64; i++) { /* make for overflow area */ + LOG (LOG_WAR, ("rate %2d , ar %f ms , dr %f ms \n", i, + ((double) (EG_ENT << ENV_BITS) / OPL->AR_TABLE[i]) * (1000.0 / + OPL->rate), + ((double) (EG_ENT << ENV_BITS) / OPL->DR_TABLE[i]) * (1000.0 / + OPL->rate))); + } +#endif +} + +/* ---------- generic table initialize ---------- */ +static int +OPLOpenTable (void) +{ + int s, t; + double rate; + int i, j; + double pom; + + /* allocate dynamic tables */ + if ((TL.TABLE = malloc (TL_MAX * 2 * sizeof (INT32))) == NULL) + return 0; + if ((SIN.TABLE = malloc (SIN_ENT * 4 * sizeof (INT32 *))) == NULL) { + free (TL.TABLE_PTR); + return 0; + } + if ((AMS.TABLE = malloc (AMS_ENT * 2 * sizeof (INT32))) == NULL) { + free (TL.TABLE_PTR); + free (SIN.TABLE_PTR); + return 0; + } + if ((VIB.TABLE = malloc (VIB_ENT * 2 * sizeof (INT32))) == NULL) { + free (TL.TABLE_PTR); + free (SIN.TABLE_PTR); + free (AMS.TABLE_PTR); + return 0; + } + /* make total level table */ + for (t = 0; t < EG_ENT - 1; t++) { + rate = ((1 << TL_BITS) - 1) / pow (10, EG_STEP * t / 20); /* dB -> voltage */ + TL.TABLE[t] = (int) rate; + TL.TABLE[TL_MAX + t] = -TL.TABLE[t]; +/* LOG(LOG_INF,("TotalLevel(%3d) = %x\n",t,TL.TABLE[t]));*/ + } + /* fill volume off area */ + for (t = EG_ENT - 1; t < TL_MAX; t++) { + TL.TABLE[t] = TL.TABLE[TL_MAX + t] = 0; + } + + /* make sinwave table (total level offet) */ + /* degree 0 = degree 180 = off */ + SIN.TABLE[0] = SIN.TABLE[SIN_ENT / 2] = &TL.TABLE[EG_ENT - 1]; + for (s = 1; s <= SIN_ENT / 4; s++) { + pom = sin (2 * PI * s / SIN_ENT); /* sin */ + pom = 20 * log10 (1 / pom); /* decibel */ + j = (int) (pom / EG_STEP); /* TL.TABLE steps */ + + /* degree 0 - 90 , degree 180 - 90 : plus section */ + SIN.TABLE[s] = SIN.TABLE[SIN_ENT / 2 - s] = &TL.TABLE[j]; + /* degree 180 - 270 , degree 360 - 270 : minus section */ + SIN.TABLE[SIN_ENT / 2 + s] = SIN.TABLE[SIN_ENT - s] = &TL.TABLE[TL_MAX + j]; +/* LOG(LOG_INF,("sin(%3d) = %f:%f db\n",s,pom,(double)j * EG_STEP));*/ + } + for (s = 0; s < SIN_ENT; s++) { + SIN.TABLE[SIN_ENT * 1 + s] = + s < (SIN_ENT / 2) ? SIN.TABLE[s] : &TL.TABLE[EG_ENT]; + SIN.TABLE[SIN_ENT * 2 + s] = SIN.TABLE[s % (SIN_ENT / 2)]; + SIN.TABLE[SIN_ENT * 3 + s] = + (s / (SIN_ENT / 4)) & 1 ? &TL.TABLE[EG_ENT] : SIN.TABLE[SIN_ENT * 2 + + s]; + } + + /* envelope counter -> envelope output table */ + for (i = 0; i < EG_ENT; i++) { + /* ATTACK curve */ + pom = pow (((double) (EG_ENT - 1 - i) / EG_ENT), 8) * EG_ENT; + /* if( pom >= EG_ENT ) pom = EG_ENT-1; */ + ENV_CURVE[i] = (int) pom; + /* DECAY ,RELEASE curve */ + ENV_CURVE[(EG_DST >> ENV_BITS) + i] = i; + } + /* off */ + ENV_CURVE[EG_OFF >> ENV_BITS] = EG_ENT - 1; + /* make LFO ams table */ + for (i = 0; i < AMS_ENT; i++) { + pom = (1.0 + sin (2 * PI * i / AMS_ENT)) / 2; /* sin */ + AMS.TABLE[i] = (INT32) ((1.0 / EG_STEP) * pom); /* 1dB */ + AMS.TABLE[AMS_ENT + i] = (INT32) ((4.8 / EG_STEP) * pom); /* 4.8dB */ + } + /* make LFO vibrate table */ + for (i = 0; i < VIB_ENT; i++) { + /* 100cent = 1seminote = 6% ?? */ + pom = (double) VIB_RATE *0.06 * sin (2 * PI * i / VIB_ENT); /* +-100sect step */ + + VIB.TABLE[i] = VIB_RATE + (INT32) (pom * 0.07); /* +- 7cent */ + VIB.TABLE[VIB_ENT + i] = VIB_RATE + (INT32) (pom * 0.14); /* +-14cent */ + /* LOG(LOG_INF,("vib %d=%d\n",i,VIB.TABLE[VIB_ENT+i])); */ + } + return 1; +} + + +static void +OPLCloseTable (void) +{ + free (TL.TABLE_PTR); + free (SIN.TABLE_PTR); + free (AMS.TABLE_PTR); + free (VIB.TABLE_PTR); +} + +/* CSM Key Controll */ +INLINE void +CSMKeyControll (OPL_CH * CH) +{ + OPL_SLOT *slot1 = &CH->SLOT[SLOT1]; + OPL_SLOT *slot2 = &CH->SLOT[SLOT2]; + + /* all key off */ + OPL_KEYOFF (slot1); + OPL_KEYOFF (slot2); + /* total level latch */ + slot1->TLL = slot1->TL + (CH->ksl_base >> slot1->ksl); + slot1->TLL = slot1->TL + (CH->ksl_base >> slot1->ksl); + /* key on */ + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON (slot1); + OPL_KEYON (slot2); +} + +/* ---------- opl initialize ---------- */ +static void +OPL_initalize (FM_OPL * OPL) +{ + int fn; + + /* frequency base */ + OPL->freqbase = (OPL->rate) ? ((double) OPL->clock / OPL->rate) / 72 : 0; + /* Timer base time */ + OPL->TimerBase = 1.0 / ((double) OPL->clock / 72.0); + /* make time tables */ + init_timetables (OPL, OPL_ARRATE, OPL_DRRATE); + /* make fnumber -> increment counter table */ + for (fn = 0; fn < 1024; fn++) { + OPL->FN_TABLE[fn] = + (UINT32) (OPL->freqbase * fn * FREQ_RATE * (1 << 7) / 2); + } + /* LFO freq.table */ + OPL->amsIncr = + (INT32) (OPL->rate ? (double) AMS_ENT * (1 << AMS_SHIFT) / OPL->rate * + 3.7 * ((double) OPL->clock / 3600000) : 0); + OPL->vibIncr = + (INT32) (OPL->rate ? (double) VIB_ENT * (1 << VIB_SHIFT) / OPL->rate * + 6.4 * ((double) OPL->clock / 3600000) : 0); +} + +/* ---------- write a OPL registers ---------- */ +static void +OPLWriteReg (FM_OPL * OPL, int r, int v) +{ + OPL_CH *CH; + int slot; + unsigned int block_fnum; + + switch (r & 0xe0) { + case 0x00: /* 00-1f:controll */ + switch (r & 0x1f) { + case 0x01: + /* wave selector enable */ + if (OPL->type & OPL_TYPE_WAVESEL) { + OPL->wavesel = v & 0x20; + if (!OPL->wavesel) { + /* preset compatible mode */ + int c; + + for (c = 0; c < OPL->max_ch; c++) { + OPL->P_CH[c].SLOT[SLOT1].wavetable = &SIN.TABLE[0]; + OPL->P_CH[c].SLOT[SLOT2].wavetable = &SIN.TABLE[0]; + } + } + } + return; + case 0x02: /* Timer 1 */ + OPL->T[0] = (256 - v) * 4; + break; + case 0x03: /* Timer 2 */ + OPL->T[1] = (256 - v) * 16; + return; + case 0x04: /* IRQ clear / mask and Timer enable */ + if (v & 0x80) { /* IRQ flag clear */ + OPL_STATUS_RESET (OPL, 0x7f); + } else { /* set IRQ mask ,timer enable */ + UINT8 st1 = v & 1; + UINT8 st2 = (v >> 1) & 1; + + /* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */ + OPL_STATUS_RESET (OPL, v & 0x78); + OPL_STATUSMASK_SET (OPL, ((~v) & 0x78) | 0x01); + /* timer 2 */ + if (OPL->st[1] != st2) { + double interval = st2 ? (double) OPL->T[1] * OPL->TimerBase : 0.0; + + OPL->st[1] = st2; + if (OPL->TimerHandler) + (OPL->TimerHandler) (OPL->TimerParam + 1, interval); + } + /* timer 1 */ + if (OPL->st[0] != st1) { + double interval = st1 ? (double) OPL->T[0] * OPL->TimerBase : 0.0; + + OPL->st[0] = st1; + if (OPL->TimerHandler) + (OPL->TimerHandler) (OPL->TimerParam + 0, interval); + } + } + return; +#if BUILD_Y8950 + case 0x06: /* Key Board OUT */ + if (OPL->type & OPL_TYPE_KEYBOARD) { + if (OPL->keyboardhandler_w) + OPL->keyboardhandler_w (OPL->keyboard_param, v); + else + LOG (LOG_WAR, ("OPL:write unmapped KEYBOARD port\n")); + } + return; + case 0x07: /* DELTA-T controll : START,REC,MEMDATA,REPT,SPOFF,x,x,RST */ + if (OPL->type & OPL_TYPE_ADPCM) + YM_DELTAT_ADPCM_Write (OPL->deltat, r - 0x07, v); + return; + case 0x08: /* MODE,DELTA-T : CSM,NOTESEL,x,x,smpl,da/ad,64k,rom */ + OPL->mode = v; + v &= 0x1f; /* for DELTA-T unit */ + case 0x09: /* START ADD */ + case 0x0a: + case 0x0b: /* STOP ADD */ + case 0x0c: + case 0x0d: /* PRESCALE */ + case 0x0e: + case 0x0f: /* ADPCM data */ + case 0x10: /* DELTA-N */ + case 0x11: /* DELTA-N */ + case 0x12: /* EG-CTRL */ + if (OPL->type & OPL_TYPE_ADPCM) + YM_DELTAT_ADPCM_Write (OPL->deltat, r - 0x07, v); + return; +#if 0 + case 0x15: /* DAC data */ + case 0x16: + case 0x17: /* SHIFT */ + return; + case 0x18: /* I/O CTRL (Direction) */ + if (OPL->type & OPL_TYPE_IO) + OPL->portDirection = v & 0x0f; + return; + case 0x19: /* I/O DATA */ + if (OPL->type & OPL_TYPE_IO) { + OPL->portLatch = v; + if (OPL->porthandler_w) + OPL->porthandler_w (OPL->port_param, v & OPL->portDirection); + } + return; + case 0x1a: /* PCM data */ + return; +#endif +#endif + } + break; + case 0x20: /* am,vib,ksr,eg type,mul */ + slot = slot_array[r & 0x1f]; + if (slot == -1) + return; + set_mul (OPL, slot, v); + return; + case 0x40: + slot = slot_array[r & 0x1f]; + if (slot == -1) + return; + set_ksl_tl (OPL, slot, v); + return; + case 0x60: + slot = slot_array[r & 0x1f]; + if (slot == -1) + return; + set_ar_dr (OPL, slot, v); + return; + case 0x80: + slot = slot_array[r & 0x1f]; + if (slot == -1) + return; + set_sl_rr (OPL, slot, v); + return; + case 0xa0: + switch (r) { + case 0xbd: + /* amsep,vibdep,r,bd,sd,tom,tc,hh */ + { + UINT8 rkey = OPL->rythm ^ v; + + OPL->ams_table = &AMS.TABLE[v & 0x80 ? AMS_ENT : 0]; + OPL->vib_table = &VIB.TABLE[v & 0x40 ? VIB_ENT : 0]; + OPL->rythm = v & 0x3f; + if (OPL->rythm & 0x20) { +#if 0 + usrintf_showmessage ("OPL Rythm mode select"); +#endif + /* BD key on/off */ + if (rkey & 0x10) { + if (v & 0x10) { + OPL->P_CH[6].op1_out[0] = OPL->P_CH[6].op1_out[1] = 0; + OPL_KEYON (&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYON (&OPL->P_CH[6].SLOT[SLOT2]); + } else { + OPL_KEYOFF (&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYOFF (&OPL->P_CH[6].SLOT[SLOT2]); + } + } + /* SD key on/off */ + if (rkey & 0x08) { + if (v & 0x08) + OPL_KEYON (&OPL->P_CH[7].SLOT[SLOT2]); + else + OPL_KEYOFF (&OPL->P_CH[7].SLOT[SLOT2]); + } /* TAM key on/off */ + if (rkey & 0x04) { + if (v & 0x04) + OPL_KEYON (&OPL->P_CH[8].SLOT[SLOT1]); + else + OPL_KEYOFF (&OPL->P_CH[8].SLOT[SLOT1]); + } + /* TOP-CY key on/off */ + if (rkey & 0x02) { + if (v & 0x02) + OPL_KEYON (&OPL->P_CH[8].SLOT[SLOT2]); + else + OPL_KEYOFF (&OPL->P_CH[8].SLOT[SLOT2]); + } + /* HH key on/off */ + if (rkey & 0x01) { + if (v & 0x01) + OPL_KEYON (&OPL->P_CH[7].SLOT[SLOT1]); + else + OPL_KEYOFF (&OPL->P_CH[7].SLOT[SLOT1]); + } + } + } + return; + } + /* keyon,block,fnum */ + if ((r & 0x0f) > 8) + return; + CH = &OPL->P_CH[r & 0x0f]; + if (!(r & 0x10)) { /* a0-a8 */ + block_fnum = (CH->block_fnum & 0x1f00) | v; + } else { /* b0-b8 */ + int keyon = (v >> 5) & 1; + + block_fnum = ((v & 0x1f) << 8) | (CH->block_fnum & 0xff); + if (CH->keyon != keyon) { + if ((CH->keyon = keyon)) { + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON (&CH->SLOT[SLOT1]); + OPL_KEYON (&CH->SLOT[SLOT2]); + } else { + OPL_KEYOFF (&CH->SLOT[SLOT1]); + OPL_KEYOFF (&CH->SLOT[SLOT2]); + } + } + } + /* update */ + if (CH->block_fnum != block_fnum) { + int blockRv = 7 - (block_fnum >> 10); + int fnum = block_fnum & 0x3ff; + + CH->block_fnum = block_fnum; + + CH->ksl_base = KSL_TABLE[block_fnum >> 6]; + CH->fc = OPL->FN_TABLE[fnum] >> blockRv; + CH->kcode = CH->block_fnum >> 9; + if ((OPL->mode & 0x40) && CH->block_fnum & 0x100) + CH->kcode |= 1; + CALC_FCSLOT (CH, &CH->SLOT[SLOT1]); + CALC_FCSLOT (CH, &CH->SLOT[SLOT2]); + } + return; + case 0xc0: + /* FB,C */ + if ((r & 0x0f) > 8) + return; + CH = &OPL->P_CH[r & 0x0f]; + { + int feedback = (v >> 1) & 7; + + CH->FB = feedback ? (8 + 1) - feedback : 0; + CH->CON = v & 1; + set_algorythm (CH); + } + return; + case 0xe0: /* wave type */ + slot = slot_array[r & 0x1f]; + if (slot == -1) + return; + CH = &OPL->P_CH[slot / 2]; + if (OPL->wavesel) { + /* LOG(LOG_INF,("OPL SLOT %d wave select %d\n",slot,v&3)); */ + CH->SLOT[slot & 1].wavetable = &SIN.TABLE[(v & 0x03) * SIN_ENT]; + } + return; + } +} + +/* lock/unlock for common table */ +static int +OPL_LockTable (void) +{ + num_lock++; + if (num_lock > 1) + return 0; + /* first time */ + cur_chip = NULL; + /* allocate total level table (128kb space) */ + if (!OPLOpenTable ()) { + num_lock--; + return -1; + } + return 0; +} + +static void +OPL_UnLockTable (void) +{ + if (num_lock) + num_lock--; + if (num_lock) + return; + /* last time */ + cur_chip = NULL; + OPLCloseTable (); +} + +#if (BUILD_YM3812 || BUILD_YM3526) +/*******************************************************************************/ +/* YM3812 local section */ +/*******************************************************************************/ + +/* ---------- update one of chip ----------- */ +void +YM3812UpdateOne (FM_OPL * OPL, INT16 * buffer, int length) +{ + int i; + int data; + FMSAMPLE *buf = buffer; + UINT32 amsCnt = OPL->amsCnt; + UINT32 vibCnt = OPL->vibCnt; + UINT8 rythm = OPL->rythm & 0x20; + OPL_CH *CH, *R_CH; + + if ((void *) OPL != cur_chip) { + cur_chip = (void *) OPL; + /* channel pointers */ + S_CH = OPL->P_CH; + E_CH = &S_CH[9]; + /* rythm slot */ + SLOT7_1 = &S_CH[7].SLOT[SLOT1]; + SLOT7_2 = &S_CH[7].SLOT[SLOT2]; + SLOT8_1 = &S_CH[8].SLOT[SLOT1]; + SLOT8_2 = &S_CH[8].SLOT[SLOT2]; + /* LFO state */ + amsIncr = OPL->amsIncr; + vibIncr = OPL->vibIncr; + ams_table = OPL->ams_table; + vib_table = OPL->vib_table; + } + R_CH = rythm ? &S_CH[6] : E_CH; + for (i = 0; i < length; i++) { + /* channel A channel B channel C */ + /* LFO */ + ams = ams_table[(amsCnt += amsIncr) >> AMS_SHIFT]; + vib = vib_table[(vibCnt += vibIncr) >> VIB_SHIFT]; + outd[0] = 0; + /* FM part */ + for (CH = S_CH; CH < R_CH; CH++) + OPL_CALC_CH (CH); + /* Rythn part */ + if (rythm) + OPL_CALC_RH (S_CH); + /* limit check */ + data = Limit (outd[0], OPL_MAXOUT, OPL_MINOUT); + /* store to sound buffer */ + buf[i] = data >> OPL_OUTSB; + } + + OPL->amsCnt = amsCnt; + OPL->vibCnt = vibCnt; +} +#endif /* (BUILD_YM3812 || BUILD_YM3526) */ + +#if BUILD_Y8950 + +void +Y8950UpdateOne (FM_OPL * OPL, INT16 * buffer, int length) +{ + int i; + int data; + FMSAMPLE *buf = buffer; + UINT32 amsCnt = OPL->amsCnt; + UINT32 vibCnt = OPL->vibCnt; + UINT8 rythm = OPL->rythm & 0x20; + OPL_CH *CH, *R_CH; + YM_DELTAT *DELTAT = OPL->deltat; + + /* setup DELTA-T unit */ + YM_DELTAT_DECODE_PRESET (DELTAT); + + if ((void *) OPL != cur_chip) { + cur_chip = (void *) OPL; + /* channel pointers */ + S_CH = OPL->P_CH; + E_CH = &S_CH[9]; + /* rythm slot */ + SLOT7_1 = &S_CH[7].SLOT[SLOT1]; + SLOT7_2 = &S_CH[7].SLOT[SLOT2]; + SLOT8_1 = &S_CH[8].SLOT[SLOT1]; + SLOT8_2 = &S_CH[8].SLOT[SLOT2]; + /* LFO state */ + amsIncr = OPL->amsIncr; + vibIncr = OPL->vibIncr; + ams_table = OPL->ams_table; + vib_table = OPL->vib_table; + } + R_CH = rythm ? &S_CH[6] : E_CH; + for (i = 0; i < length; i++) { + /* channel A channel B channel C */ + /* LFO */ + ams = ams_table[(amsCnt += amsIncr) >> AMS_SHIFT]; + vib = vib_table[(vibCnt += vibIncr) >> VIB_SHIFT]; + outd[0] = 0; + /* deltaT ADPCM */ + if (DELTAT->flag) + YM_DELTAT_ADPCM_CALC (DELTAT); + /* FM part */ + for (CH = S_CH; CH < R_CH; CH++) + OPL_CALC_CH (CH); + /* Rythn part */ + if (rythm) + OPL_CALC_RH (S_CH); + /* limit check */ + data = Limit (outd[0], OPL_MAXOUT, OPL_MINOUT); + /* store to sound buffer */ + buf[i] = data >> OPL_OUTSB; + } + OPL->amsCnt = amsCnt; + OPL->vibCnt = vibCnt; + /* deltaT START flag */ + if (!DELTAT->flag) + OPL->status &= 0xfe; +} +#endif + +/* ---------- reset one of chip ---------- */ +void +OPLResetChip (FM_OPL * OPL) +{ + int c, s; + int i; + + /* reset chip */ + OPL->mode = 0; /* normal mode */ + OPL_STATUS_RESET (OPL, 0x7f); + /* reset with register write */ + OPLWriteReg (OPL, 0x01, 0); /* wabesel disable */ + OPLWriteReg (OPL, 0x02, 0); /* Timer1 */ + OPLWriteReg (OPL, 0x03, 0); /* Timer2 */ + OPLWriteReg (OPL, 0x04, 0); /* IRQ mask clear */ + for (i = 0xff; i >= 0x20; i--) + OPLWriteReg (OPL, i, 0); + /* reset OPerator paramater */ + for (c = 0; c < OPL->max_ch; c++) { + OPL_CH *CH = &OPL->P_CH[c]; + + /* OPL->P_CH[c].PAN = OPN_CENTER; */ + for (s = 0; s < 2; s++) { + /* wave table */ + CH->SLOT[s].wavetable = &SIN.TABLE[0]; + /* CH->SLOT[s].evm = ENV_MOD_RR; */ + CH->SLOT[s].evc = EG_OFF; + CH->SLOT[s].eve = EG_OFF + 1; + CH->SLOT[s].evs = 0; + } + } +#if BUILD_Y8950 + if (OPL->type & OPL_TYPE_ADPCM) { + YM_DELTAT *DELTAT = OPL->deltat; + + DELTAT->freqbase = OPL->freqbase; + DELTAT->output_pointer = outd; + DELTAT->portshift = 5; + DELTAT->output_range = DELTAT_MIXING_LEVEL << TL_BITS; + YM_DELTAT_ADPCM_Reset (DELTAT, 0); + } +#endif +} + +/* ---------- Create one of vietual YM3812 ---------- */ +/* 'rate' is sampling rate and 'bufsiz' is the size of the */ +FM_OPL * +OPLCreate (int type, int clock, int rate) +{ + char *ptr; + FM_OPL *OPL; + int state_size; + int max_ch = 9; /* normaly 9 channels */ + + if (OPL_LockTable () == -1) + return NULL; + /* allocate OPL state space */ + state_size = sizeof (FM_OPL); + state_size += sizeof (OPL_CH) * max_ch; +#if BUILD_Y8950 + if (type & OPL_TYPE_ADPCM) + state_size += sizeof (YM_DELTAT); +#endif + /* allocate memory block */ + ptr = malloc (state_size); + if (ptr == NULL) + return NULL; + /* clear */ + memset (ptr, 0, state_size); + OPL = (FM_OPL *) ptr; + ptr += sizeof (FM_OPL); + OPL->P_CH = (OPL_CH *) ptr; + ptr += sizeof (OPL_CH) * max_ch; +#if BUILD_Y8950 + if (type & OPL_TYPE_ADPCM) + OPL->deltat = (YM_DELTAT *) ptr; + ptr += sizeof (YM_DELTAT); +#endif + /* set channel state pointer */ + OPL->type = type; + OPL->clock = clock; + OPL->rate = rate; + OPL->max_ch = max_ch; + /* init grobal tables */ + OPL_initalize (OPL); + /* reset chip */ + OPLResetChip (OPL); + return OPL; +} + +/* ---------- Destroy one of vietual YM3812 ---------- */ +void +OPLDestroy (FM_OPL * OPL) +{ + void *t = OPL; + + OPL_UnLockTable (); + free (t); +} + +/* ---------- Option handlers ---------- */ + +void +OPLSetTimerHandler (FM_OPL * OPL, OPL_TIMERHANDLER TimerHandler, + int channelOffset) +{ + OPL->TimerHandler = TimerHandler; + OPL->TimerParam = channelOffset; +} + +void +OPLSetIRQHandler (FM_OPL * OPL, OPL_IRQHANDLER IRQHandler, int param) +{ + OPL->IRQHandler = IRQHandler; + OPL->IRQParam = param; +} + +void +OPLSetUpdateHandler (FM_OPL * OPL, OPL_UPDATEHANDLER UpdateHandler, int param) +{ + OPL->UpdateHandler = UpdateHandler; + OPL->UpdateParam = param; +} + +#if BUILD_Y8950 +void +OPLSetPortHandler (FM_OPL * OPL, OPL_PORTHANDLER_W PortHandler_w, + OPL_PORTHANDLER_R PortHandler_r, int param) +{ + OPL->porthandler_w = PortHandler_w; + OPL->porthandler_r = PortHandler_r; + OPL->port_param = param; +} + +void +OPLSetKeyboardHandler (FM_OPL * OPL, OPL_PORTHANDLER_W KeyboardHandler_w, + OPL_PORTHANDLER_R KeyboardHandler_r, int param) +{ + OPL->keyboardhandler_w = KeyboardHandler_w; + OPL->keyboardhandler_r = KeyboardHandler_r; + OPL->keyboard_param = param; +} +#endif +/* ---------- YM3812 I/O interface ---------- */ +int +OPLWrite (FM_OPL * OPL, int a, int v) +{ + if (!(a & 1)) { /* address port */ + OPL->address = v & 0xff; + } else { /* data port */ + if (OPL->UpdateHandler) + OPL->UpdateHandler (OPL->UpdateParam, 0); + OPLWriteReg (OPL, OPL->address, v); + } + return OPL->status >> 7; +} + +unsigned char +OPLRead (FM_OPL * OPL, int a) +{ + if (!(a & 1)) { /* status port */ + return OPL->status & (OPL->statusmask | 0x80); + } + /* data port */ + switch (OPL->address) { + case 0x05: /* KeyBoard IN */ + if (OPL->type & OPL_TYPE_KEYBOARD) { + if (OPL->keyboardhandler_r) + return OPL->keyboardhandler_r (OPL->keyboard_param); + else + LOG (LOG_WAR, ("OPL:read unmapped KEYBOARD port\n")); + } + return 0; +#if 0 + case 0x0f: /* ADPCM-DATA */ + return 0; +#endif + case 0x19: /* I/O DATA */ + if (OPL->type & OPL_TYPE_IO) { + if (OPL->porthandler_r) + return OPL->porthandler_r (OPL->port_param); + else + LOG (LOG_WAR, ("OPL:read unmapped I/O port\n")); + } + return 0; + case 0x1a: /* PCM-DATA */ + return 0; + } + return 0; +} + +int +OPLTimerOver (FM_OPL * OPL, int c) +{ + if (c) { /* Timer B */ + OPL_STATUS_SET (OPL, 0x20); + } else { /* Timer A */ + OPL_STATUS_SET (OPL, 0x40); + /* CSM mode key,TL controll */ + if (OPL->mode & 0x80) { /* CSM mode total level latch and auto key on */ + int ch; + + if (OPL->UpdateHandler) + OPL->UpdateHandler (OPL->UpdateParam, 0); + for (ch = 0; ch < 9; ch++) + CSMKeyControll (&OPL->P_CH[ch]); + } + } + /* reload timer */ + if (OPL->TimerHandler) + (OPL->TimerHandler) (OPL->TimerParam + c, + (double) OPL->T[c] * OPL->TimerBase); + return OPL->status >> 7; +} diff --git a/gst/nsf/fmopl.h b/gst/nsf/fmopl.h new file mode 100644 index 00000000..92a2859d --- /dev/null +++ b/gst/nsf/fmopl.h @@ -0,0 +1,164 @@ +#ifndef __FMOPL_H_ +#define __FMOPL_H_ + +#define HAS_YM3812 1 +typedef signed short int FMSAMPLE; + + +#define BUILD_YM3812 (HAS_YM3812) +#define BUILD_YM3526 (HAS_YM3526) +#define BUILD_Y8950 (HAS_Y8950) + +/* compiler dependence */ +#ifndef OSD_CPU_H +#define OSD_CPU_H +typedef unsigned char UINT8; /* unsigned 8bit */ +typedef unsigned short UINT16; /* unsigned 16bit */ +typedef unsigned int UINT32; /* unsigned 32bit */ +typedef signed char INT8; /* signed 8bit */ +typedef signed short INT16; /* signed 16bit */ +typedef signed int INT32; /* signed 32bit */ +#endif + +#if BUILD_Y8950 +#include "ymdeltat.h" +#endif + +typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec); +typedef void (*OPL_IRQHANDLER)(int param,int irq); +typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us); +typedef void (*OPL_PORTHANDLER_W)(int param,unsigned char data); +typedef unsigned char (*OPL_PORTHANDLER_R)(int param); + +/* !!!!! here is private section , do not access there member direct !!!!! */ + +#define OPL_TYPE_WAVESEL 0x01 /* waveform select */ +#define OPL_TYPE_ADPCM 0x02 /* DELTA-T ADPCM unit */ +#define OPL_TYPE_KEYBOARD 0x04 /* keyboard interface */ +#define OPL_TYPE_IO 0x08 /* I/O port */ + +/* ---------- OPL one of slot ---------- */ +typedef struct fm_opl_slot { + INT32 TL; /* total level :TL << 8 */ + INT32 TLL; /* adjusted now TL */ + UINT8 KSR; /* key scale rate :(shift down bit) */ + INT32 *AR; /* attack rate :&AR_TABLE[AR<<2] */ + INT32 *DR; /* decay rate :&DR_TALBE[DR<<2] */ + INT32 SL; /* sustin level :SL_TALBE[SL] */ + INT32 *RR; /* release rate :&DR_TABLE[RR<<2] */ + UINT8 ksl; /* keyscale level :(shift down bits) */ + UINT8 ksr; /* key scale rate :kcode>>KSR */ + UINT32 mul; /* multiple :ML_TABLE[ML] */ + UINT32 Cnt; /* frequency count : */ + UINT32 Incr; /* frequency step : */ + /* envelope generator state */ + UINT8 eg_typ; /* envelope type flag */ + UINT8 evm; /* envelope phase */ + INT32 evc; /* envelope counter */ + INT32 eve; /* envelope counter end point */ + INT32 evs; /* envelope counter step */ + INT32 evsa; /* envelope step for AR :AR[ksr] */ + INT32 evsd; /* envelope step for DR :DR[ksr] */ + INT32 evsr; /* envelope step for RR :RR[ksr] */ + /* LFO */ + UINT8 ams; /* ams flag */ + UINT8 vib; /* vibrate flag */ + /* wave selector */ + INT32 **wavetable; +}OPL_SLOT; + +/* ---------- OPL one of channel ---------- */ +typedef struct fm_opl_channel { + OPL_SLOT SLOT[2]; + UINT8 CON; /* connection type */ + UINT8 FB; /* feed back :(shift down bit) */ + INT32 *connect1; /* slot1 output pointer */ + INT32 *connect2; /* slot2 output pointer */ + INT32 op1_out[2]; /* slot1 output for selfeedback */ + /* phase generator state */ + UINT32 block_fnum; /* block+fnum : */ + UINT8 kcode; /* key code : KeyScaleCode */ + UINT32 fc; /* Freq. Increment base */ + UINT32 ksl_base; /* KeyScaleLevel Base step */ + UINT8 keyon; /* key on/off flag */ +} OPL_CH; + +/* OPL state */ +typedef struct fm_opl_f { + UINT8 type; /* chip type */ + int clock; /* master clock (Hz) */ + int rate; /* sampling rate (Hz) */ + double freqbase; /* frequency base */ + double TimerBase; /* Timer base time (==sampling time) */ + UINT8 address; /* address register */ + UINT8 status; /* status flag */ + UINT8 statusmask; /* status mask */ + UINT32 mode; /* Reg.08 : CSM , notesel,etc. */ + /* Timer */ + int T[2]; /* timer counter */ + UINT8 st[2]; /* timer enable */ + /* FM channel slots */ + OPL_CH *P_CH; /* pointer of CH */ + int max_ch; /* maximum channel */ + /* Rythm sention */ + UINT8 rythm; /* Rythm mode , key flag */ +#if BUILD_Y8950 + /* Delta-T ADPCM unit (Y8950) */ + YM_DELTAT *deltat; /* DELTA-T ADPCM */ +#endif + /* Keyboard / I/O interface unit (Y8950) */ + UINT8 portDirection; + UINT8 portLatch; + OPL_PORTHANDLER_R porthandler_r; + OPL_PORTHANDLER_W porthandler_w; + int port_param; + OPL_PORTHANDLER_R keyboardhandler_r; + OPL_PORTHANDLER_W keyboardhandler_w; + int keyboard_param; + /* time tables */ + INT32 AR_TABLE[75]; /* atttack rate tables */ + INT32 DR_TABLE[75]; /* decay rate tables */ + UINT32 FN_TABLE[1024]; /* fnumber -> increment counter */ + /* LFO */ + INT32 *ams_table; + INT32 *vib_table; + INT32 amsCnt; + INT32 amsIncr; + INT32 vibCnt; + INT32 vibIncr; + /* wave selector enable flag */ + UINT8 wavesel; + /* external event callback handler */ + OPL_TIMERHANDLER TimerHandler; /* TIMER handler */ + int TimerParam; /* TIMER parameter */ + OPL_IRQHANDLER IRQHandler; /* IRQ handler */ + int IRQParam; /* IRQ parameter */ + OPL_UPDATEHANDLER UpdateHandler; /* stream update handler */ + int UpdateParam; /* stream update parameter */ +} FM_OPL; + +/* ---------- Generic interface section ---------- */ +#define OPL_TYPE_YM3526 (0) +#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL) +#define OPL_TYPE_Y8950 (OPL_TYPE_ADPCM|OPL_TYPE_KEYBOARD|OPL_TYPE_IO) + +FM_OPL *OPLCreate(int type, int clock, int rate); +void OPLDestroy(FM_OPL *OPL); +void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER TimerHandler,int channelOffset); +void OPLSetIRQHandler(FM_OPL *OPL,OPL_IRQHANDLER IRQHandler,int param); +void OPLSetUpdateHandler(FM_OPL *OPL,OPL_UPDATEHANDLER UpdateHandler,int param); +/* Y8950 port handlers */ +void OPLSetPortHandler(FM_OPL *OPL,OPL_PORTHANDLER_W PortHandler_w,OPL_PORTHANDLER_R PortHandler_r,int param); +void OPLSetKeyboardHandler(FM_OPL *OPL,OPL_PORTHANDLER_W KeyboardHandler_w,OPL_PORTHANDLER_R KeyboardHandler_r,int param); + +void OPLResetChip(FM_OPL *OPL); +int OPLWrite(FM_OPL *OPL,int a,int v); +unsigned char OPLRead(FM_OPL *OPL,int a); +int OPLTimerOver(FM_OPL *OPL,int c); + +/* YM3626/YM3812 local section */ +void YM3812UpdateOne(FM_OPL *OPL, INT16 *buffer, int length); + +void Y8950UpdateOne(FM_OPL *OPL, INT16 *buffer, int length); + +#endif diff --git a/gst/nsf/gstnsf.c b/gst/nsf/gstnsf.c new file mode 100644 index 00000000..8f3b53ee --- /dev/null +++ b/gst/nsf/gstnsf.c @@ -0,0 +1,635 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> + * (C) <2004> Johan Dahlin <johan@gnome.org> + * (C) <2006> Wim Taymans <wim@fluendo.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> +#include "gstnsf.h" + +#define GST_CAT_DEFAULT nsfdec_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +static const GstElementDetails gst_nsfdec_details = +GST_ELEMENT_DETAILS ("Nsf decoder", + "Codec/Audio/Decoder", + "Using nosefart to decode NSF audio tunes", + "Johan Dahlin <johan@gnome.org>"); + +/* Nsfdec signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +#define DEFAULT_TUNE 0 +#define DEFAULT_FILTER NSF_FILTER_NONE + +enum +{ + PROP_0, + PROP_TUNE, + PROP_FILTER, +}; + +static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-nsf") + ); + +static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw-int, " + "endianness = (int) BYTE_ORDER, " + "signed = (boolean) TRUE, " + "width = (int) { 8, 16 }, " + "depth = (int) { 8, 16 }, " + "rate = (int) [ 8000, 48000 ], " "channels = (int) [ 1, 2 ]") + ); + +#define GST_TYPE_NSF_FILTER (gst_nsf_filter_get_type()) +static GType +gst_nsf_filter_get_type (void) +{ + static GType nsf_filter_type = 0; + static GEnumValue nsf_filter[] = { + {NSF_FILTER_NONE, "NSF_FILTER_NONE", "None"}, + {NSF_FILTER_LOWPASS, "NSF_FILTER_LOWPASS", "Lowpass"}, + {NSF_FILTER_WEIGHTED, "NSF_FILTER_WEIGHTED", "Weighted"}, + {0, NULL, NULL}, + }; + + if (!nsf_filter_type) { + nsf_filter_type = g_enum_register_static ("GstNsfFilter", nsf_filter); + } + return nsf_filter_type; +} + + +static void gst_nsfdec_base_init (gpointer g_class); +static void gst_nsfdec_class_init (GstNsfDec * klass); +static void gst_nsfdec_init (GstNsfDec * nsfdec); +static void gst_nsfdec_finalize (GObject * object); + +static GstFlowReturn gst_nsfdec_chain (GstPad * pad, GstBuffer * buffer); +static gboolean gst_nsfdec_sink_event (GstPad * pad, GstEvent * event); + +static gboolean gst_nsfdec_src_convert (GstPad * pad, GstFormat src_format, + gint64 src_value, GstFormat * dest_format, gint64 * dest_value); +static gboolean gst_nsfdec_src_event (GstPad * pad, GstEvent * event); +static gboolean gst_nsfdec_src_query (GstPad * pad, GstQuery * query); + +static void gst_nsfdec_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_nsfdec_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); + +static GstElementClass *parent_class = NULL; + +//static guint gst_nsfdec_signals[LAST_SIGNAL] = { 0 }; + +GType +gst_nsfdec_get_type (void) +{ + static GType nsfdec_type = 0; + + if (G_UNLIKELY (nsfdec_type == 0)) { + static const GTypeInfo nsfdec_info = { + sizeof (GstNsfDecClass), + gst_nsfdec_base_init, + NULL, + (GClassInitFunc) gst_nsfdec_class_init, + NULL, + NULL, + sizeof (GstNsfDec), + 0, + (GInstanceInitFunc) gst_nsfdec_init, + NULL + }; + + nsfdec_type = + g_type_register_static (GST_TYPE_ELEMENT, "GstNsfDec", &nsfdec_info, + (GTypeFlags) 0); + } + + return nsfdec_type; +} + +static void +gst_nsfdec_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_set_details (element_class, &gst_nsfdec_details); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_templ)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_templ)); +} + +static void +gst_nsfdec_class_init (GstNsfDec * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + parent_class = GST_ELEMENT_CLASS (g_type_class_peek_parent (klass)); + + gobject_class->finalize = gst_nsfdec_finalize; + gobject_class->set_property = gst_nsfdec_set_property; + gobject_class->get_property = gst_nsfdec_get_property; + + g_object_class_install_property (gobject_class, PROP_TUNE, + g_param_spec_int ("tune", "tune", "tune", 1, 100, 1, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_FILTER, + g_param_spec_enum ("filter", "filter", "filter", GST_TYPE_NSF_FILTER, + NSF_FILTER_NONE, G_PARAM_WRITABLE)); + + GST_DEBUG_CATEGORY_INIT (nsfdec_debug, "nsfdec", 0, + "NES sound file (nsf) decoder"); + + nsf_init (); +} + +static void +gst_nsfdec_init (GstNsfDec * nsfdec) +{ + nsfdec->sinkpad = + gst_pad_new_from_template (gst_static_pad_template_get (&sink_templ), + "sink"); + gst_pad_set_query_function (nsfdec->sinkpad, NULL); + gst_pad_set_event_function (nsfdec->sinkpad, gst_nsfdec_sink_event); + gst_pad_set_chain_function (nsfdec->sinkpad, gst_nsfdec_chain); + gst_element_add_pad (GST_ELEMENT (nsfdec), nsfdec->sinkpad); + + nsfdec->srcpad = + gst_pad_new_from_template (gst_static_pad_template_get (&src_templ), + "src"); + gst_pad_set_event_function (nsfdec->srcpad, gst_nsfdec_src_event); + gst_pad_set_query_function (nsfdec->srcpad, gst_nsfdec_src_query); + gst_pad_use_fixed_caps (nsfdec->srcpad); + gst_element_add_pad (GST_ELEMENT (nsfdec), nsfdec->srcpad); + + nsfdec->nsf = NULL; + nsfdec->state = NSF_STATE_NEED_TUNE; + nsfdec->tune_buffer = NULL; + + nsfdec->blocksize = 0; + nsfdec->frequency = 44100; + nsfdec->bits = 8; + nsfdec->stereo = FALSE; + nsfdec->channels = 1; + + nsfdec->tune_number = DEFAULT_TUNE; + nsfdec->filter = DEFAULT_FILTER; +} + +static void +gst_nsfdec_finalize (GObject * object) +{ + GstNsfDec *nsfdec = GST_NSFDEC (object); + + if (nsfdec->tune_buffer) + gst_buffer_unref (nsfdec->tune_buffer); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +nsfdec_negotiate (GstNsfDec * nsfdec) +{ + GstCaps *allowed; + gboolean sign = TRUE; + gint width = 16, depth = 16; + GstStructure *structure; + int rate = 44100; + int channels = 1; + GstCaps *caps; + + allowed = gst_pad_get_allowed_caps (nsfdec->srcpad); + if (!allowed) + goto nothing_allowed; + + GST_DEBUG_OBJECT (nsfdec, "allowed caps: %" GST_PTR_FORMAT, allowed); + + structure = gst_caps_get_structure (allowed, 0); + + gst_structure_get_int (structure, "width", &width); + gst_structure_get_int (structure, "depth", &depth); + + if (width && depth && width != depth) + goto wrong_width; + + width = width | depth; + if (width) { + nsfdec->bits = width; + } + + gst_structure_get_boolean (structure, "signed", &sign); + gst_structure_get_int (structure, "rate", &rate); + nsfdec->frequency = rate; + gst_structure_get_int (structure, "channels", &channels); + nsfdec->channels = channels; + nsfdec->stereo = (channels == 2); + + caps = gst_caps_new_simple ("audio/x-raw-int", + "endianness", G_TYPE_INT, G_BYTE_ORDER, + "signed", G_TYPE_BOOLEAN, TRUE, + "width", G_TYPE_INT, nsfdec->bits, + "depth", G_TYPE_INT, nsfdec->bits, + "rate", G_TYPE_INT, nsfdec->frequency, + "channels", G_TYPE_INT, nsfdec->channels, NULL); + gst_pad_set_caps (nsfdec->srcpad, caps); + gst_caps_unref (caps); + + gst_caps_unref (allowed); + + return TRUE; + +nothing_allowed: + { + GST_DEBUG_OBJECT (nsfdec, "could not get allowed caps"); + return FALSE; + } +wrong_width: + { + GST_DEBUG_OBJECT (nsfdec, "width %d and depth %d are different", + width, depth); + gst_caps_unref (allowed); + return FALSE; + } +} + +static void +play_loop (GstPad * pad) +{ + GstFlowReturn ret; + GstNsfDec *nsfdec; + GstBuffer *out; + gint64 value, offset, time; + GstFormat format; + + nsfdec = GST_NSFDEC (gst_pad_get_parent (pad)); + + out = gst_buffer_new_and_alloc (nsfdec->blocksize); + gst_buffer_set_caps (out, GST_PAD_CAPS (pad)); + + nsf_frame (nsfdec->nsf); + apu_process (GST_BUFFER_DATA (out), nsfdec->blocksize / nsfdec->bps); + + /* get offset in samples */ + format = GST_FORMAT_DEFAULT; + gst_nsfdec_src_convert (nsfdec->srcpad, + GST_FORMAT_BYTES, nsfdec->total_bytes, &format, &offset); + GST_BUFFER_OFFSET (out) = offset; + + /* get current timestamp */ + format = GST_FORMAT_TIME; + gst_nsfdec_src_convert (nsfdec->srcpad, + GST_FORMAT_BYTES, nsfdec->total_bytes, &format, &time); + GST_BUFFER_TIMESTAMP (out) = time; + + /* update position and get new timestamp to calculate duration */ + nsfdec->total_bytes += nsfdec->blocksize; + + /* get offset in samples */ + format = GST_FORMAT_DEFAULT; + gst_nsfdec_src_convert (nsfdec->srcpad, + GST_FORMAT_BYTES, nsfdec->total_bytes, &format, &value); + GST_BUFFER_OFFSET_END (out) = value; + + format = GST_FORMAT_TIME; + gst_nsfdec_src_convert (nsfdec->srcpad, + GST_FORMAT_BYTES, nsfdec->total_bytes, &format, &value); + GST_BUFFER_DURATION (out) = value - time; + + if ((ret = gst_pad_push (nsfdec->srcpad, out)) != GST_FLOW_OK) + goto pause; + +done: + gst_object_unref (nsfdec); + + return; + + /* ERRORS */ +pause: + { + const gchar *reason = gst_flow_get_name (ret); + + GST_DEBUG_OBJECT (nsfdec, "pausing task, reason %s", reason); + gst_pad_pause_task (pad); + + if (GST_FLOW_IS_FATAL (ret) || ret == GST_FLOW_NOT_LINKED) { + if (ret == GST_FLOW_UNEXPECTED) { + /* perform EOS logic, FIXME, segment seek? */ + gst_pad_push_event (pad, gst_event_new_eos ()); + } else { + /* for fatal errors we post an error message */ + GST_ELEMENT_ERROR (nsfdec, STREAM, FAILED, + (NULL), ("streaming task paused, reason %s", reason)); + gst_pad_push_event (pad, gst_event_new_eos ()); + } + } + goto done; + } +} + +static gboolean +start_play_tune (GstNsfDec * nsfdec) +{ + gboolean res; + + nsfdec->nsf = nsf_load (NULL, GST_BUFFER_DATA (nsfdec->tune_buffer), + GST_BUFFER_SIZE (nsfdec->tune_buffer)); + + if (!nsfdec->nsf) + goto could_not_load; + + if (!nsfdec_negotiate (nsfdec)) + goto could_not_negotiate; + + nsf_playtrack (nsfdec->nsf, + nsfdec->tune_number, nsfdec->frequency, nsfdec->bits, nsfdec->stereo); + nsf_setfilter (nsfdec->nsf, nsfdec->filter); + + nsfdec->bps = (nsfdec->bits >> 3) * nsfdec->channels; + /* calculate the number of bytes we need to output after each call to + * nsf_frame(). */ + nsfdec->blocksize = + nsfdec->bps * nsfdec->frequency / nsfdec->nsf->playback_rate; + + gst_pad_push_event (nsfdec->srcpad, + gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, 0, -1, 0)); + + res = gst_pad_start_task (nsfdec->srcpad, + (GstTaskFunction) play_loop, nsfdec->srcpad); + + return res; + + /* ERRORS */ +could_not_load: + { + GST_ELEMENT_ERROR (nsfdec, LIBRARY, INIT, + ("Could not load tune"), ("Could not load tune")); + return FALSE; + } +could_not_negotiate: + { + GST_ELEMENT_ERROR (nsfdec, CORE, NEGOTIATION, + ("Could not negotiate format"), ("Could not negotiate format")); + return FALSE; + } +} + +static gboolean +gst_nsfdec_sink_event (GstPad * pad, GstEvent * event) +{ + GstNsfDec *nsfdec; + gboolean res; + + nsfdec = GST_NSFDEC (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + res = start_play_tune (nsfdec); + break; + case GST_EVENT_NEWSEGMENT: + res = FALSE; + break; + default: + res = FALSE; + break; + } + gst_event_unref (event); + gst_object_unref (nsfdec); + + return res; +} + +static GstFlowReturn +gst_nsfdec_chain (GstPad * pad, GstBuffer * buffer) +{ + GstNsfDec *nsfdec; + + nsfdec = GST_NSFDEC (gst_pad_get_parent (pad)); + + /* collect all data, we start doing something when we get an EOS + * event */ + if (nsfdec->tune_buffer) { + nsfdec->tune_buffer = gst_buffer_join (nsfdec->tune_buffer, buffer); + } else { + nsfdec->tune_buffer = buffer; + } + + gst_object_unref (nsfdec); + + return GST_FLOW_OK; +} + +static gboolean +gst_nsfdec_src_convert (GstPad * pad, GstFormat src_format, gint64 src_value, + GstFormat * dest_format, gint64 * dest_value) +{ + gboolean res = TRUE; + guint scale = 1; + GstNsfDec *nsfdec; + + nsfdec = GST_NSFDEC (gst_pad_get_parent (pad)); + + if (src_format == *dest_format) { + *dest_value = src_value; + return TRUE; + } + + switch (src_format) { + case GST_FORMAT_BYTES: + switch (*dest_format) { + case GST_FORMAT_DEFAULT: + if (nsfdec->bps == 0) + return FALSE; + *dest_value = src_value / nsfdec->bps; + break; + case GST_FORMAT_TIME: + { + gint byterate = nsfdec->bps * nsfdec->frequency; + + if (byterate == 0) + return FALSE; + *dest_value = + gst_util_uint64_scale_int (src_value, GST_SECOND, byterate); + break; + } + default: + res = FALSE; + } + break; + case GST_FORMAT_DEFAULT: + switch (*dest_format) { + case GST_FORMAT_BYTES: + *dest_value = src_value * nsfdec->bps; + break; + case GST_FORMAT_TIME: + if (nsfdec->frequency == 0) + return FALSE; + *dest_value = + gst_util_uint64_scale_int (src_value, GST_SECOND, + nsfdec->frequency); + break; + default: + res = FALSE; + } + break; + case GST_FORMAT_TIME: + switch (*dest_format) { + case GST_FORMAT_BYTES: + scale = nsfdec->bps; + /* fallthrough */ + case GST_FORMAT_DEFAULT: + *dest_value = + gst_util_uint64_scale_int (src_value, scale * nsfdec->frequency, + GST_SECOND); + break; + default: + res = FALSE; + } + break; + default: + res = FALSE; + } + + return res; +} + +static gboolean +gst_nsfdec_src_event (GstPad * pad, GstEvent * event) +{ + gboolean res = FALSE; + GstNsfDec *nsfdec; + + nsfdec = GST_NSFDEC (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + default: + break; + } + gst_event_unref (event); + + gst_object_unref (nsfdec); + + return res; +} + +static gboolean +gst_nsfdec_src_query (GstPad * pad, GstQuery * query) +{ + gboolean res = TRUE; + GstNsfDec *nsfdec; + + nsfdec = GST_NSFDEC (gst_pad_get_parent (pad)); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + { + GstFormat format; + gint64 current; + + gst_query_parse_position (query, &format, NULL); + + /* we only know about our bytes, convert to requested format */ + res &= gst_nsfdec_src_convert (pad, + GST_FORMAT_BYTES, nsfdec->total_bytes, &format, ¤t); + if (res) { + gst_query_set_position (query, format, current); + } + break; + } + default: + res = gst_pad_query_default (pad, query); + break; + } + gst_object_unref (nsfdec); + + return res; +} + +static void +gst_nsfdec_set_property (GObject * object, guint prop_id, const GValue * value, + GParamSpec * pspec) +{ + GstNsfDec *nsfdec; + + nsfdec = GST_NSFDEC (object); + + switch (prop_id) { + case PROP_TUNE: + nsfdec->tune_number = g_value_get_int (value); + break; + case PROP_FILTER: + nsfdec->filter = g_value_get_enum (value); + if (nsfdec->nsf) + nsf_setfilter (nsfdec->nsf, nsfdec->filter); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + return; + } +} + +static void +gst_nsfdec_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstNsfDec *nsfdec; + + nsfdec = GST_NSFDEC (object); + + switch (prop_id) { + case PROP_TUNE: + g_value_set_int (value, nsfdec->tune_number); + break; + case PROP_FILTER: + g_value_set_enum (value, nsfdec->filter); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "nsfdec", GST_RANK_PRIMARY, + GST_TYPE_NSFDEC); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, + "nsfdec", + "Uses nosefart to decode .nsf files", + plugin_init, VERSION, "GPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/gst/nsf/gstnsf.h b/gst/nsf/gstnsf.h new file mode 100644 index 00000000..c80f4e98 --- /dev/null +++ b/gst/nsf/gstnsf.h @@ -0,0 +1,86 @@ +/* Copyright (C) 2003 Johan Dahlin <johan@gnome.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __GST_NSFDEC_H__ +#define __GST_NSFDEC_H__ + +#include <stdlib.h> + +#include <gst/gst.h> + +#include "types.h" +#include "nsf.h" + +G_BEGIN_DECLS + +#define GST_TYPE_NSFDEC \ + (gst_nsfdec_get_type()) +#define GST_NSFDEC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_NSFDEC,GstNsfDec)) +#define GST_NSFDEC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_NSFDEC,GstNsfDec)) +#define GST_IS_NSFDEC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_NSFDEC)) +#define GST_IS_NSFDEC_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_NSFDEC)) + +typedef struct _GstNsfDec GstNsfDec; +typedef struct _GstNsfDecClass GstNsfDecClass; + +enum +{ + NSF_STATE_NEED_TUNE = 1, + NSF_STATE_LOAD_TUNE = 2, + NSF_STATE_PLAY_TUNE = 3 +}; + +struct _GstNsfDec { + GstElement element; + + /* pads */ + GstPad *sinkpad, + *srcpad; + + gint state; + GstBuffer *tune_buffer; + guint64 total_bytes; + + /* properties */ + gint tune_number; + gint filter; + + nsf_t *nsf; + gulong blocksize; + + int frequency; + int bits; + gboolean stereo; + int channels; + int bps; +}; + +struct _GstNsfDecClass { + GstElementClass parent_class; +}; + +GType gst_nsfdec_get_type (void); + +G_END_DECLS + +#endif /* __GST_NSFDEC_H__ */ diff --git a/gst/nsf/log.c b/gst/nsf/log.c new file mode 100644 index 00000000..5d0d168a --- /dev/null +++ b/gst/nsf/log.c @@ -0,0 +1,148 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** log.c +** +** Error logging functions +** $Id$ +*/ + +#include <stdio.h> +#include <stdarg.h> +#include "types.h" +#include "log.h" + + +#ifdef OSD_LOG +#include "osd.h" +#endif + +#if defined(OSD_LOG) && !defined(NOFRENDO_DEBUG) +#error NOFRENDO_DEBUG must be defined as well as OSD_LOG +#endif + +/* Note that all of these functions will be empty if +** debugging is not enabled. +*/ +#ifdef NOFRENDO_DEBUG +static FILE *errorlog; +#endif + +int +log_init (void) +{ +#ifdef NOFRENDO_DEBUG +#ifdef OSD_LOG + /* Initialize an OSD logging system */ + osd_loginit (); +#endif /* OSD_LOG */ + errorlog = fopen ("errorlog.txt", "wt"); + if (NULL == errorlog) + return (-1); +#endif /* NOFRENDO_DEBUG */ + return 0; +} + +void +log_shutdown (void) +{ +#ifdef NOFRENDO_DEBUG + /* Snoop around for unallocated blocks */ + mem_checkblocks (); + mem_checkleaks (); +#ifdef OSD_LOG + osd_logshutdown (); +#endif /* OSD_LOG */ + fclose (errorlog); +#endif /* NOFRENDO_DEBUG */ +} + +void +log_print (const char *string) +{ +#ifdef NOFRENDO_DEBUG +#ifdef OSD_LOG + osd_logprint (string); +#endif /* OSD_LOG */ + /* Log it to disk, as well */ + fputs (string, errorlog); +#endif /* NOFRENDO_DEBUG */ +} + +void +log_printf (const char *format, ...) +{ +#ifdef NOFRENDO_DEBUG +#ifdef OSD_LOG + char buffer[1024 + 1]; +#endif /* OSD_LOG */ + va_list arg; + + va_start (arg, format); + +#ifdef OSD_LOG + vsprintf (buffer, format, arg); + osd_logprint (buffer); +#endif /* OSD_LOG */ + vfprintf (errorlog, format, arg); + va_end (arg); +#endif /* NOFRENDO_DEBUG */ +} + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.5 2000/06/26 04:55:33 matt +** minor change +** +** Revision 1.4 2000/06/09 15:12:25 matt +** initial revision +** +*/ diff --git a/gst/nsf/log.h b/gst/nsf/log.h new file mode 100644 index 00000000..96c37ea4 --- /dev/null +++ b/gst/nsf/log.h @@ -0,0 +1,76 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** log.h +** +** Error logging header file +** $Id$ +*/ + +#ifndef _LOG_H_ +#define _LOG_H_ + +#include <stdio.h> + +extern int log_init(void); +extern void log_shutdown(void); +extern void log_print(const char *string); +extern void log_printf(const char *format, ...); + +#endif /* _LOG_H_ */ + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.4 2000/06/09 15:12:25 matt +** initial revision +** +*/ + diff --git a/gst/nsf/memguard.c b/gst/nsf/memguard.c new file mode 100644 index 00000000..8ad9749d --- /dev/null +++ b/gst/nsf/memguard.c @@ -0,0 +1,398 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** memguard.c +** +** memory allocation wrapper routines +** +** NOTE: based on code (c) 1998 the Retrocade group +** $Id$ +*/ + +#include "types.h" + +/* undefine macro definitions, so we get real calls */ +#undef malloc +#undef free + +#include <string.h> +#include <stdlib.h> +#include "memguard.h" +#include "log.h" + + +/* Maximum number of allocated blocks at any one time */ +#define MAX_BLOCKS 16384 + +/* Memory block structure */ +typedef struct memblock_s +{ + void *block_addr; + int block_size; + char *file_name; + int line_num; +} memblock_t; + +boolean mem_debug = TRUE; /* debugging flag */ + + +#ifdef NOFRENDO_DEBUG + +static int mem_blockcount = 0; /* allocated block count */ +static memblock_t *mem_record = NULL; + +#define GUARD_STRING "GgUuAaRrDdSsTtRrIiNnGgBbLlOoCcKk" +#define GUARD_LENGTH 64 /* before and after allocated block */ + + +/* +** Check the memory guard to make sure out of bounds writes have not +** occurred. +*/ +static boolean +mem_checkguardblock (void *data, int guard_size) +{ + uint8 *orig, *chk, *blk; + int i, alloc_size; + + /* get the original pointer */ + orig = (((uint8 *) data) - guard_size); + + /* get the size */ + alloc_size = *((uint32 *) orig); + + /* now skip past the size */ + blk = orig + sizeof (uint32); + + /* check leading guard string */ + chk = GUARD_STRING; + for (i = sizeof (uint32); i < guard_size; i++) { + if (0 == *chk) + chk = GUARD_STRING; + if (*blk != *chk) + return FALSE; + chk++; + blk++; + } + + /* check end of block */ + chk = GUARD_STRING; + blk = ((uint8 *) data) + alloc_size; + for (i = 0; i < guard_size; i++) { + if (0 == *chk) + chk = GUARD_STRING; + if (*blk != *chk) + return FALSE; + chk++; + blk++; + } + + /* we're okay! */ + return TRUE; +} + +/* free a guard block */ +static void +mem_freeguardblock (void *data, int guard_size) +{ + uint8 *orig = (((uint8 *) data) - guard_size); + + free (orig); +} + +/* fill in the memory guard, advance the pointer to the 'real' memory */ +static void * +mem_guardblock (int alloc_size, int guard_size) +{ + void *orig; + uint8 *blk, *chk; + int i; + + /* allocate memory */ + orig = calloc (alloc_size + (guard_size * 2), 1); + if (NULL == orig) + return NULL; + + blk = ((uint8 *) orig); + + /* store the size of the newly allocated block */ + *((uint32 *) blk) = alloc_size; + + /* skip past the size */ + blk += sizeof (uint32); + + /* put guard string at beginning of block */ + chk = GUARD_STRING; + for (i = sizeof (uint32); i < guard_size; i++) { + if (0 == *chk) + chk = GUARD_STRING; + *blk++ = *chk++; + } + + /* check end of block */ + chk = GUARD_STRING; + blk = guard_size + (uint8 *) orig + alloc_size; + for (i = 0; i < guard_size; i++) { + if (0 == *chk) + chk = GUARD_STRING; + *blk++ = *chk++; + } + + return (void *) (guard_size + (uint8 *) orig); +} + + +/* Allocate a bunch of memory to keep track of all memory blocks */ +static void +mem_init (void) +{ + if (mem_record) { + free (mem_record); + mem_record = NULL; + } + + mem_record = calloc (MAX_BLOCKS * sizeof (memblock_t), 1); + ASSERT (mem_record); +} + +/* add a block of memory to the master record */ +static void +mem_addblock (void *data, int block_size, char *file, int line) +{ + int i; + + for (i = 0; i < MAX_BLOCKS; i++) { + if (NULL == mem_record[i].block_addr) { + mem_record[i].block_addr = data; + mem_record[i].block_size = block_size; + mem_record[i].file_name = file; + mem_record[i].line_num = line; + return; + } + } + + ASSERT_MSG ("out of memory blocks."); +} + +/* find an entry in the block record and delete it */ +static void +mem_deleteblock (void *data, char *file, int line) +{ + int i; + char fail[256]; + + for (i = 0; i < MAX_BLOCKS; i++) { + if (data == mem_record[i].block_addr) { + if (FALSE == mem_checkguardblock (mem_record[i].block_addr, GUARD_LENGTH)) { + sprintf (fail, + "mem_deleteblock 0x%08X at line %d of %s -- block corrupt", + (uint32) data, line, file); + ASSERT_MSG (fail); + } + + memset (&mem_record[i], 0, sizeof (memblock_t)); + return; + } + } + + sprintf (fail, "mem_deleteblock 0x%08X at line %d of %s -- block not found", + (uint32) data, line, file); + ASSERT_MSG (fail); +} +#endif /* NOFRENDO_DEBUG */ + +/* allocates memory and clears it */ +#ifdef NOFRENDO_DEBUG +void * +_my_malloc (int size, char *file, int line) +#else +void * +_my_malloc (int size) +#endif +{ + void *temp; + char fail[256]; + +#ifdef NOFRENDO_DEBUG + if (NULL == mem_record && FALSE != mem_debug) + mem_init (); + + if (FALSE != mem_debug) + temp = mem_guardblock (size, GUARD_LENGTH); + else +#endif /* NOFRENDO_DEBUG */ + temp = calloc (sizeof (uint8), size); + + if (NULL == temp) { +#ifdef NOFRENDO_DEBUG + sprintf (fail, "malloc: out of memory at line %d of %s. block size: %d\n", + line, file, size); +#else + sprintf (fail, "malloc: out of memory. block size: %d\n", size); +#endif + ASSERT_MSG (fail); + } +#ifdef NOFRENDO_DEBUG + if (FALSE != mem_debug) + mem_addblock (temp, size, file, line); + + mem_blockcount++; +#endif + + return temp; +} + +/* free a pointer allocated with my_malloc */ +#ifdef NOFRENDO_DEBUG +void +_my_free (void **data, char *file, int line) +#else +void +_my_free (void **data) +#endif +{ + char fail[256]; + + if (NULL == data || NULL == *data + || 0xFFFFFFFF == (uint32) * data || 0xFFFFFFFF == (uint32) data) { +#ifdef NOFRENDO_DEBUG + sprintf (fail, "free: attempted to free NULL pointer at line %d of %s\n", + line, file); +#else + sprintf (fail, "free: attempted to free NULL pointer.\n"); +#endif + ASSERT_MSG (fail); + } +#ifdef NOFRENDO_DEBUG + /* if this is true, we are in REAL trouble */ + if (0 == mem_blockcount) { + ASSERT_MSG ("free: attempted to free memory when no blocks available"); + } + + if (FALSE != mem_debug) + mem_deleteblock (*data, file, line); + + mem_blockcount--; /* dec our block count */ + + if (FALSE != mem_debug) + mem_freeguardblock (*data, GUARD_LENGTH); + else +#endif /* NOFRENDO_DEBUG */ + free (*data); + + *data = NULL; /* NULL our source */ +} + +/* check for orphaned memory handles */ +void +mem_checkleaks (void) +{ +#ifdef NOFRENDO_DEBUG + int i; + + if (FALSE == mem_debug) + return; + + if (mem_blockcount) { + log_printf ("memory leak - %d unfreed block%s\n\n", mem_blockcount, + mem_blockcount == 1 ? "" : "s"); + + for (i = 0; i < MAX_BLOCKS; i++) { + if (mem_record[i].block_addr) { + log_printf ("addr: 0x%08X, size: %d, line %d of %s%s\n", + (uint32) mem_record[i].block_addr, + mem_record[i].block_size, + mem_record[i].line_num, + mem_record[i].file_name, + (FALSE == mem_checkguardblock (mem_record[i].block_addr, + GUARD_LENGTH)) + ? " -- block corrupt" : ""); + } + } + } else + log_printf ("no memory leaks\n"); +#endif +} + +void +mem_checkblocks (void) +{ +#ifdef NOFRENDO_DEBUG + int i; + + if (FALSE == mem_debug) + return; + + for (i = 0; i < MAX_BLOCKS; i++) { + if (mem_record[i].block_addr) { + if (FALSE == mem_checkguardblock (mem_record[i].block_addr, GUARD_LENGTH)) { + log_printf ("addr: 0x%08X, size: %d, line %d of %s -- block corrupt\n", + (uint32) mem_record[i].block_addr, + mem_record[i].block_size, + mem_record[i].line_num, mem_record[i].file_name); + } + } + } +#endif /* NOFRENDO_DEBUG */ +} + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.8 2000/06/26 04:54:48 matt +** simplified and made more robust +** +** Revision 1.7 2000/06/12 01:11:41 matt +** cleaned up some error output for win32 +** +** Revision 1.6 2000/06/09 15:12:25 matt +** initial revision +** +*/ diff --git a/gst/nsf/memguard.h b/gst/nsf/memguard.h new file mode 100644 index 00000000..2074a9e6 --- /dev/null +++ b/gst/nsf/memguard.h @@ -0,0 +1,95 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** memguard.h +** +** memory allocation wrapper routines +** $Id$ +*/ + +#ifndef _MEMGUARD_H_ +#define _MEMGUARD_H_ + +#ifdef NOFRENDO_DEBUG + +#define malloc(s) _my_malloc((s), __FILE__, __LINE__) +#define free(d) _my_free((void **) &(d), __FILE__, __LINE__) + +extern void *_my_malloc(int size, char *file, int line); +extern void _my_free(void **data, char *file, int line); + +#else /* Non-debugging versions of calls */ + +#define malloc(s) _my_malloc((s)) +#define free(d) _my_free((void **) &(d)) + +extern void *_my_malloc(int size); +extern void _my_free(void **data); + +#endif /* NOFRENDO_DEBUG */ + + +extern void mem_checkblocks(void); +extern void mem_checkleaks(void); + +extern boolean mem_debug; + +#endif /* _MEMGUARD_H_ */ + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.5 2000/06/26 04:54:48 matt +** simplified and made more robust +** +** Revision 1.4 2000/06/09 15:12:25 matt +** initial revision +** +*/ diff --git a/gst/nsf/mmc5_snd.c b/gst/nsf/mmc5_snd.c new file mode 100644 index 00000000..7c39af44 --- /dev/null +++ b/gst/nsf/mmc5_snd.c @@ -0,0 +1,383 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** mmc5_snd.c +** +** Nintendo MMC5 sound emulation +** $Id$ +*/ + +#include <string.h> +#include "types.h" +#include "mmc5_snd.h" +#include "nes_apu.h" + +/* TODO: encapsulate apu/mmc5 rectangle */ + +#define APU_OVERSAMPLE +#define APU_VOLUME_DECAY(x) ((x) -= ((x) >> 7)) + + +typedef struct mmc5dac_s +{ + int32 output; + boolean enabled; +} mmc5dac_t; + + +/* look up table madness */ +static int32 decay_lut[16]; +static int vbl_lut[32]; + +/* various sound constants for sound emulation */ +/* vblank length table used for rectangles, triangle, noise */ +static const uint8 vbl_length[32] = { + 5, 127, 10, 1, 19, 2, 40, 3, 80, 4, 30, 5, 7, 6, 13, 7, + 6, 8, 12, 9, 24, 10, 48, 11, 96, 12, 36, 13, 8, 14, 16, 15 +}; + +/* ratios of pos/neg pulse for rectangle waves +** 2/16 = 12.5%, 4/16 = 25%, 8/16 = 50%, 12/16 = 75% +** (4-bit adder in rectangles, hence the 16) +*/ +static const int duty_lut[4] = { + 2, 4, 8, 12 +}; + + +static int32 mmc5_incsize; +static uint8 mul[2]; +static mmc5rectangle_t mmc5rect[2]; +static mmc5dac_t mmc5dac; + +#define MMC5_RECTANGLE_OUTPUT chan->output_vol +static int32 +mmc5_rectangle (mmc5rectangle_t * chan) +{ + int32 output; + +#ifdef APU_OVERSAMPLE + int num_times; + int32 total; +#endif /* APU_OVERSAMPLE */ + + /* reg0: 0-3=volume, 4=envelope, 5=hold, 6-7=duty cycle + ** reg1: 0-2=sweep shifts, 3=sweep inc/dec, 4-6=sweep length, 7=sweep on + ** reg2: 8 bits of freq + ** reg3: 0-2=high freq, 7-4=vbl length counter + */ + + APU_VOLUME_DECAY (chan->output_vol); + + if (FALSE == chan->enabled || 0 == chan->vbl_length) + return MMC5_RECTANGLE_OUTPUT; + + /* vbl length counter */ + if (FALSE == chan->holdnote) + chan->vbl_length--; + + /* envelope decay at a rate of (env_delay + 1) / 240 secs */ + chan->env_phase -= 4; /* 240/60 */ + while (chan->env_phase < 0) { + chan->env_phase += chan->env_delay; + + if (chan->holdnote) + chan->env_vol = (chan->env_vol + 1) & 0x0F; + else if (chan->env_vol < 0x0F) + chan->env_vol++; + } + + if (chan->freq < APU_TO_FIXED (4)) + return MMC5_RECTANGLE_OUTPUT; + + chan->phaseacc -= mmc5_incsize; /* # of cycles per sample */ + if (chan->phaseacc >= 0) + return MMC5_RECTANGLE_OUTPUT; + +#ifdef APU_OVERSAMPLE + num_times = total = 0; + + if (chan->fixed_envelope) + output = chan->volume << 8; /* fixed volume */ + else + output = (chan->env_vol ^ 0x0F) << 8; +#endif + + while (chan->phaseacc < 0) { + chan->phaseacc += chan->freq; + chan->adder = (chan->adder + 1) & 0x0F; + +#ifdef APU_OVERSAMPLE + if (chan->adder < chan->duty_flip) + total += output; + else + total -= output; + + num_times++; +#endif + } + +#ifdef APU_OVERSAMPLE + chan->output_vol = total / num_times; +#else + if (chan->fixed_envelope) + output = chan->volume << 8; /* fixed volume */ + else + output = (chan->env_vol ^ 0x0F) << 8; + + if (0 == chan->adder) + chan->output_vol = output; + else if (chan->adder == chan->duty_flip) + chan->output_vol = -output; +#endif + + return MMC5_RECTANGLE_OUTPUT; +} + +static uint8 +mmc5_read (uint32 address) +{ + uint32 retval; + + retval = (uint32) (mul[0] * mul[1]); + + switch (address) { + case 0x5205: + return (uint8) retval; + + case 0x5206: + return (uint8) (retval >> 8); + + default: + return 0xFF; + } +} + +/* mix vrcvi sound channels together */ +static int32 +mmc5_process (void) +{ + int32 accum; + + accum = mmc5_rectangle (&mmc5rect[0]); + accum += mmc5_rectangle (&mmc5rect[1]); + if (mmc5dac.enabled) + accum += mmc5dac.output; + + return accum; +} + +/* write to registers */ +static void +mmc5_write (uint32 address, uint8 value) +{ + int chan; + + switch (address) { + /* rectangles */ + case MMC5_WRA0: + case MMC5_WRB0: + chan = (address & 4) ? 1 : 0; + mmc5rect[chan].regs[0] = value; + + mmc5rect[chan].volume = value & 0x0F; + mmc5rect[chan].env_delay = decay_lut[value & 0x0F]; + mmc5rect[chan].holdnote = (value & 0x20) ? TRUE : FALSE; + mmc5rect[chan].fixed_envelope = (value & 0x10) ? TRUE : FALSE; + mmc5rect[chan].duty_flip = duty_lut[value >> 6]; + break; + + case MMC5_WRA1: + case MMC5_WRB1: + break; + + case MMC5_WRA2: + case MMC5_WRB2: + chan = (address & 4) ? 1 : 0; + mmc5rect[chan].regs[2] = value; + if (mmc5rect[chan].enabled) + mmc5rect[chan].freq = + APU_TO_FIXED ((((mmc5rect[chan].regs[3] & 7) << 8) + value) + 1); + break; + + case MMC5_WRA3: + case MMC5_WRB3: + chan = (address & 4) ? 1 : 0; + mmc5rect[chan].regs[3] = value; + + if (mmc5rect[chan].enabled) { + mmc5rect[chan].vbl_length = vbl_lut[value >> 3]; + mmc5rect[chan].env_vol = 0; + mmc5rect[chan].freq = + APU_TO_FIXED ((((value & 7) << 8) + mmc5rect[chan].regs[2]) + 1); + mmc5rect[chan].adder = 0; + } + break; + + case MMC5_SMASK: + if (value & 0x01) + mmc5rect[0].enabled = TRUE; + else { + mmc5rect[0].enabled = FALSE; + mmc5rect[0].vbl_length = 0; + } + + if (value & 0x02) + mmc5rect[1].enabled = TRUE; + else { + mmc5rect[1].enabled = FALSE; + mmc5rect[1].vbl_length = 0; + } + + break; + + case 0x5010: + if (value & 0x01) + mmc5dac.enabled = TRUE; + else + mmc5dac.enabled = FALSE; + break; + + case 0x5011: + mmc5dac.output = (value ^ 0x80) << 8; + break; + + case 0x5205: + mul[0] = value; + break; + + case 0x5206: + mul[1] = value; + break; + + default: + break; + } +} + +/* reset state of vrcvi sound channels */ +static void +mmc5_reset (void) +{ + int i; + + /* get the phase period from the apu */ + mmc5_incsize = apu_getcyclerate (); + + for (i = 0x5000; i < 0x5008; i++) + mmc5_write (i, 0); + + mmc5_write (0x5010, 0); + mmc5_write (0x5011, 0); +} + +static void +mmc5_init (void) +{ + int i; + int num_samples = apu_getcontext ()->num_samples; + + /* lut used for enveloping and frequency sweeps */ + for (i = 0; i < 16; i++) + decay_lut[i] = num_samples * (i + 1); + + /* used for note length, based on vblanks and size of audio buffer */ + for (i = 0; i < 32; i++) + vbl_lut[i] = vbl_length[i] * num_samples; +} + +/* TODO: bleh */ +static void +mmc5_shutdown (void) +{ +} + +static apu_memread mmc5_memread[] = { + {0x5205, 0x5206, mmc5_read}, + {-1, -1, NULL} +}; + +static apu_memwrite mmc5_memwrite[] = { + {0x5000, 0x5015, mmc5_write}, + {0x5205, 0x5206, mmc5_write}, + {-1, -1, NULL} +}; + +apuext_t mmc5_ext = { + mmc5_init, + mmc5_shutdown, + mmc5_reset, + mmc5_process, + mmc5_memread, + mmc5_memwrite +}; + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.6 2000/07/04 04:51:41 matt +** cleanups +** +** Revision 1.5 2000/07/03 02:18:53 matt +** much better external module exporting +** +** Revision 1.4 2000/06/28 22:03:51 matt +** fixed stupid oversight +** +** Revision 1.3 2000/06/20 20:46:58 matt +** minor cleanups +** +** Revision 1.2 2000/06/20 04:06:16 matt +** migrated external sound definition to apu module +** +** Revision 1.1 2000/06/20 00:06:47 matt +** initial revision +** +*/ diff --git a/gst/nsf/mmc5_snd.h b/gst/nsf/mmc5_snd.h new file mode 100644 index 00000000..5107c58a --- /dev/null +++ b/gst/nsf/mmc5_snd.h @@ -0,0 +1,109 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** mmc5_snd.h +** +** Nintendo MMC5 sound emulation header +** $Id$ +*/ + +#ifndef _MMC5_SND_H_ +#define _MMC5_SND_H_ + +#define MMC5_WRA0 0x5000 +#define MMC5_WRA1 0x5001 +#define MMC5_WRA2 0x5002 +#define MMC5_WRA3 0x5003 +#define MMC5_WRB0 0x5004 +#define MMC5_WRB1 0x5005 +#define MMC5_WRB2 0x5006 +#define MMC5_WRB3 0x5007 +#define MMC5_SMASK 0x5015 + +typedef struct mmc5rectangle_s +{ + uint8 regs[4]; + + boolean enabled; + + int32 phaseacc; + int32 freq; + int32 output_vol; + boolean fixed_envelope; + boolean holdnote; + uint8 volume; + + int32 env_phase; + int32 env_delay; + uint8 env_vol; + + int vbl_length; + uint8 adder; + int duty_flip; +} mmc5rectangle_t; + + +#include "nes_apu.h" + +extern apuext_t mmc5_ext; + +#endif /* !_MMC5_SND_H_ */ + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.2 2000/06/20 04:06:16 matt +** migrated external sound definition to apu module +** +** Revision 1.1 2000/06/20 00:06:47 matt +** initial revision +** +*/ + diff --git a/gst/nsf/nes6502.c b/gst/nsf/nes6502.c new file mode 100644 index 00000000..c677a5ae --- /dev/null +++ b/gst/nsf/nes6502.c @@ -0,0 +1,2438 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** nes6502.c +** +** NES custom 6502 (2A03) CPU implementation +** $Id$ +*/ + + +#include "types.h" +#include "nes6502.h" +#include "dis6502.h" +#include <stdio.h> + + +#define ADD_CYCLES(x) instruction_cycles += (x) +#define INC_CYCLES() instruction_cycles++ +/* #define ADD_CYCLES(x) remaining_cycles -= (x) */ +/* #define INC_CYCLES() remaining_cycles-- */ + +/* +** Check to see if an index reg addition overflowed to next page +*/ +#define CHECK_INDEX_OVERFLOW(addr, reg) \ +{ \ + if ((uint8) (addr) < (reg)) \ + INC_CYCLES(); \ +} + +/* +** Addressing mode macros +*/ + +#define NO_READ(value) /* empty */ + +#define IMMEDIATE_BYTE(value) \ +{ \ + value = bank_readbyte(PC++); \ +} + + +#define ABSOLUTE_ADDR(address) \ +{ \ + address = bank_readaddress(PC); \ + PC += 2; \ +} + +#define ABSOLUTE(address, value) \ +{ \ + ABSOLUTE_ADDR(address); \ + value = mem_read(address); \ +} + +#define ABSOLUTE_BYTE(value) \ +{ \ + ABSOLUTE(temp, value); \ +} + +#define ABS_IND_X_ADDR(address) \ +{ \ + address = (bank_readaddress(PC) + X) & 0xFFFF; \ + PC += 2; \ + CHECK_INDEX_OVERFLOW(address, X); \ +} + +#define ABS_IND_X(address, value) \ +{ \ + ABS_IND_X_ADDR(address); \ + value = mem_read(address); \ +} + +#define ABS_IND_X_BYTE(value) \ +{ \ + ABS_IND_X(temp, value); \ +} + +#define ABS_IND_Y_ADDR(address) \ +{ \ + address = (bank_readaddress(PC) + Y) & 0xFFFF; \ + PC += 2; \ + CHECK_INDEX_OVERFLOW(address, Y); \ +} + +#define ABS_IND_Y(address, value) \ +{ \ + ABS_IND_Y_ADDR(address); \ + value = mem_read(address); \ +} + +#define ABS_IND_Y_BYTE(value) \ +{ \ + ABS_IND_Y(temp, value); \ +} + +#define ZERO_PAGE_ADDR(address) \ +{ \ + IMMEDIATE_BYTE(address); \ +} + +#define ZERO_PAGE(address, value) \ +{ \ + ZERO_PAGE_ADDR(address); \ + value = ZP_READ(address); \ +} + +#define ZERO_PAGE_BYTE(value) \ +{ \ + ZERO_PAGE(btemp, value); \ +} + +/* Zero-page indexed Y doesn't really exist, just for LDX / STX */ +#define ZP_IND_X_ADDR(address) \ +{ \ + address = bank_readbyte(PC++) + X; \ +} + +#define ZP_IND_X(bAddr, value) \ +{ \ + ZP_IND_X_ADDR(bAddr); \ + value = ZP_READ(bAddr); \ +} + +#define ZP_IND_X_BYTE(value) \ +{ \ + ZP_IND_X(btemp, value); \ +} + +#define ZP_IND_Y_ADDR(address) \ +{ \ + address = bank_readbyte(PC++) + Y; \ +} + +#define ZP_IND_Y(address, value) \ +{ \ + ZP_IND_Y_ADDR(address); \ + value = ZP_READ(address); \ +} + +#define ZP_IND_Y_BYTE(value) \ +{ \ + ZP_IND_Y(btemp, value); \ +} + +/* +** For conditional jump relative instructions +** (BCC, BCS, BEQ, BMI, BNE, BPL, BVC, BVS) +*/ +#define RELATIVE_JUMP(cond) \ +{ \ + if (cond) \ + { \ + IMMEDIATE_BYTE(btemp); \ + if (((int8) btemp + (uint8) PC) & 0xFF00) \ + ADD_CYCLES(4); \ + else \ + ADD_CYCLES(3); \ + PC += ((int8) btemp); \ + } \ + else \ + { \ + PC++; \ + ADD_CYCLES(2); \ + } \ +} + +/* +** This is actually indexed indirect, but I call it +** indirect X to avoid confusion +*/ +#define INDIR_X_ADDR(address) \ +{ \ + btemp = bank_readbyte(PC++) + X; \ + address = zp_address(btemp); \ +} + +#define INDIR_X(address, value) \ +{ \ + INDIR_X_ADDR(address); \ + value = mem_read(address); \ +} + +#define INDIR_X_BYTE(value) \ +{ \ + INDIR_X(temp, value); \ +} + +/* +** This is actually indirect indexed, but I call it +** indirect y to avoid confusion +*/ +#define INDIR_Y_ADDR(address) \ +{ \ + IMMEDIATE_BYTE(btemp); \ + address = (zp_address(btemp) + Y) & 0xFFFF; \ + /* ???? */ \ + CHECK_INDEX_OVERFLOW(address, Y); \ +} + +#define INDIR_Y(address, value) \ +{ \ + INDIR_Y_ADDR(address); \ + value = mem_read(address); \ +} + +#define INDIR_Y_BYTE(value) \ +{ \ + /*IMMEDIATE_BYTE(btemp); \ + temp = zp_address(btemp) + Y; \ + CHECK_INDEX_OVERFLOW(temp, Y); \ + value = mem_read(temp);*/ \ + INDIR_Y(temp, value); \ +} + + +#define JUMP(address) PC = bank_readaddress((address)) + +/* +** Interrupt macros +*/ +#define NMI() \ +{ \ + PUSH(PC >> 8); \ + PUSH(PC & 0xFF); \ + CLEAR_FLAG(B_FLAG); \ + PUSH(P); \ + SET_FLAG(I_FLAG); \ + JUMP(NMI_VECTOR); \ + int_pending &= ~NMI_MASK; \ + ADD_CYCLES(INT_CYCLES); \ +} + +#define IRQ() \ +{ \ + PUSH(PC >> 8); \ + PUSH(PC & 0xFF); \ + CLEAR_FLAG(B_FLAG); \ + PUSH(P); \ + SET_FLAG(I_FLAG); \ + JUMP(IRQ_VECTOR); \ + int_pending &= ~IRQ_MASK; \ + ADD_CYCLES(INT_CYCLES); \ +} + +/* +** Instruction macros +*/ + +/* Warning! NES CPU has no decimal mode, so by default this does no BCD! */ +#ifdef NES6502_DECIMAL +#define ADC(cycles, read_func) \ +{ \ + read_func(data); \ + if (P & D_FLAG) \ + { \ + temp = (A & 0x0F) + (data & 0x0F) + (P & C_FLAG); \ + if (temp >= 10) \ + temp = (temp - 10) | 0x10; \ + temp += (A & 0xF0) + (data & 0xF0); \ + TEST_AND_FLAG(0 == ((A + data + (P & C_FLAG)) & 0xFF), Z_FLAG); \ + TEST_AND_FLAG(temp & 0x80, N_FLAG); \ + TEST_AND_FLAG(((~(A ^ data)) & (A ^ temp) & 0x80), V_FLAG); \ + if (temp > 0x9F) \ + temp += 0x60; \ + TEST_AND_FLAG(temp > 0xFF, C_FLAG); \ + A = (uint8) temp; \ + } \ + else \ + { \ + temp = A + data + (P & C_FLAG); \ + /* Set C on carry */ \ + TEST_AND_FLAG(temp > 0xFF, C_FLAG); \ + /* Set V on overflow */ \ + TEST_AND_FLAG(((~(A ^ data)) & (A ^ temp) & 0x80), V_FLAG); \ + A = (uint8) temp; \ + SET_NZ_FLAGS(A); \ + }\ + ADD_CYCLES(cycles); \ +} +#else +#define ADC(cycles, read_func) \ +{ \ + read_func(data); \ + temp = A + data + (P & C_FLAG); \ + /* Set C on carry */ \ + TEST_AND_FLAG(temp > 0xFF, C_FLAG); \ + /* Set V on overflow */ \ + TEST_AND_FLAG(((~(A ^ data)) & (A ^ temp) & 0x80), V_FLAG); \ + A = (uint8) temp; \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(cycles); \ +} +#endif /* NES6502_DECIMAL */ + +/* undocumented */ +#define ANC(cycles, read_func) \ +{ \ + read_func(data); \ + A &= data; \ + SET_NZ_FLAGS(A); \ + TEST_AND_FLAG(P & N_FLAG, C_FLAG); \ + ADD_CYCLES(cycles); \ +} + +#define AND(cycles, read_func) \ +{ \ + read_func(data); \ + A &= data; \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(cycles); \ +} + +/* undocumented */ +#define ANE(cycles, read_func) \ +{ \ + read_func(data); \ + A = (A | 0xEE) & X & data; \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(cycles); \ +} + +/* undocumented */ +#ifdef NES6502_DECIMAL +#define ARR(cycles, read_func) \ +{ \ + read_func(data); \ + data &= A; \ + if (P & D_FLAG) \ + { \ + temp = (data >> 1) | ((P & C_FLAG) << 7); \ + SET_NZ_FLAGS(temp); \ + TEST_AND_FLAG((temp ^ data) & 0x40, V_FLAG); \ + if (((data & 0x0F) + (data & 0x01)) > 5) \ + temp = (temp & 0xF0) | ((temp + 0x6) & 0x0F); \ + if (((data & 0xF0) + (data & 0x10)) > 0x50) \ + { \ + temp = (temp & 0x0F) | ((temp + 0x60) & 0xF0); \ + SET_FLAG(C_FLAG); \ + } \ + else \ + CLEAR_FLAG(C_FLAG); \ + A = (uint8) temp; \ + } \ + else \ + { \ + A = (data >> 1) | ((P & C_FLAG) << 7); \ + SET_NZ_FLAGS(A); \ + TEST_AND_FLAG(A & 0x40, C_FLAG); \ + TEST_AND_FLAG(((A >> 6) ^ (A >> 5)) & 1, V_FLAG); \ + }\ + ADD_CYCLES(cycles); \ +} +#else +#define ARR(cycles, read_func) \ +{ \ + read_func(data); \ + data &= A; \ + A = (data >> 1) | ((P & C_FLAG) << 7); \ + SET_NZ_FLAGS(A); \ + TEST_AND_FLAG(A & 0x40, C_FLAG); \ + TEST_AND_FLAG((A >> 6) ^ (A >> 5), V_FLAG); \ + ADD_CYCLES(cycles); \ +} +#endif /* NES6502_DECIMAL */ + +#define ASL(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr, data); \ + TEST_AND_FLAG(data & 0x80, C_FLAG); \ + data <<= 1; \ + write_func(addr, data); \ + SET_NZ_FLAGS(data); \ + ADD_CYCLES(cycles); \ +} + +#define ASL_A() \ +{ \ + TEST_AND_FLAG(A & 0x80, C_FLAG); \ + A <<= 1; \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(2); \ +} + +/* undocumented */ +#define ASR(cycles, read_func) \ +{ \ + read_func(data); \ + data &= A; \ + TEST_AND_FLAG(data & 0x01, C_FLAG); \ + A = data >> 1; \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(cycles); \ +} + +#define BCC() \ +{ \ + RELATIVE_JUMP((IS_FLAG_CLEAR(C_FLAG))); \ +} + +#define BCS() \ +{ \ + RELATIVE_JUMP((IS_FLAG_SET(C_FLAG))); \ +} + +#define BEQ() \ +{ \ + RELATIVE_JUMP((IS_FLAG_SET(Z_FLAG))); \ +} + +#define BIT(cycles, read_func) \ +{ \ + read_func(data); \ + TEST_AND_FLAG(0 == (data & A), Z_FLAG);\ + CLEAR_FLAG(N_FLAG | V_FLAG); \ + /* move bit 7/6 of data into N/V flags */ \ + SET_FLAG(data & (N_FLAG | V_FLAG)); \ + ADD_CYCLES(cycles); \ +} + +#define BMI() \ +{ \ + RELATIVE_JUMP((IS_FLAG_SET(N_FLAG))); \ +} + +#define BNE() \ +{ \ + RELATIVE_JUMP((IS_FLAG_CLEAR(Z_FLAG))); \ +} + +#define BPL() \ +{ \ + RELATIVE_JUMP((IS_FLAG_CLEAR(N_FLAG))); \ +} + +/* Software interrupt type thang */ +#define BRK() \ +{ \ + PC++; \ + PUSH(PC >> 8); \ + PUSH(PC & 0xFF); \ + SET_FLAG(B_FLAG); \ + PUSH(P); \ + SET_FLAG(I_FLAG); \ + JUMP(IRQ_VECTOR); \ + ADD_CYCLES(7); \ +} + +#define BVC() \ +{ \ + RELATIVE_JUMP((IS_FLAG_CLEAR(V_FLAG))); \ +} + +#define BVS() \ +{ \ + RELATIVE_JUMP((IS_FLAG_SET(V_FLAG))); \ +} + +#define CLC() \ +{ \ + CLEAR_FLAG(C_FLAG); \ + ADD_CYCLES(2); \ +} + +#define CLD() \ +{ \ + CLEAR_FLAG(D_FLAG); \ + ADD_CYCLES(2); \ +} + +#define CLI() \ +{ \ + CLEAR_FLAG(I_FLAG); \ + ADD_CYCLES(2); \ +} + +#define CLV() \ +{ \ + CLEAR_FLAG(V_FLAG); \ + ADD_CYCLES(2); \ +} + +/* TODO: ick! */ +#define _COMPARE(reg, value) \ +{ \ + temp = (reg) - (value); \ + /* C is clear when data > A */ \ + TEST_AND_FLAG(0 == (temp & 0x8000), C_FLAG); \ + SET_NZ_FLAGS((uint8) temp); /* handles Z flag */ \ +} + +#define CMP(cycles, read_func) \ +{ \ + read_func(data); \ + _COMPARE(A, data); \ + ADD_CYCLES(cycles); \ +} + +#define CPX(cycles, read_func) \ +{ \ + read_func(data); \ + _COMPARE(X, data); \ + ADD_CYCLES(cycles); \ +} + +#define CPY(cycles, read_func) \ +{ \ + read_func(data); \ + _COMPARE(Y, data); \ + ADD_CYCLES(cycles); \ +} + +/* undocumented */ +#define DCP(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr, data); \ + data--; \ + write_func(addr, data); \ + CMP(cycles, NO_READ); \ +} + +#define DEC(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr, data); \ + data--; \ + write_func(addr, data); \ + SET_NZ_FLAGS(data); \ + ADD_CYCLES(cycles); \ +} + +#define DEX() \ +{ \ + X--; \ + SET_NZ_FLAGS(X); \ + ADD_CYCLES(2); \ +} + +#define DEY() \ +{ \ + Y--; \ + SET_NZ_FLAGS(Y); \ + ADD_CYCLES(2); \ +} + +/* undocumented (double-NOP) */ +#define DOP(cycles) \ +{ \ + PC++; \ + ADD_CYCLES(cycles); \ +} + +#define EOR(cycles, read_func) \ +{ \ + read_func(data); \ + A ^= data; \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(cycles); \ +} + +#define INC(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr, data); \ + data++; \ + write_func(addr, data); \ + SET_NZ_FLAGS(data); \ + ADD_CYCLES(cycles); \ +} + +#define INX() \ +{ \ + X++; \ + SET_NZ_FLAGS(X); \ + ADD_CYCLES(2); \ +} + +#define INY() \ +{ \ + Y++; \ + SET_NZ_FLAGS(Y); \ + ADD_CYCLES(2); \ +} + +/* undocumented */ +#define ISB(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr, data); \ + data++; \ + write_func(addr, data); \ + SBC(cycles, NO_READ); \ +} + +#ifdef NES6502_TESTOPS +#define JAM() \ +{ \ + cpu_Jam(); \ +} +#elif defined(NSF_PLAYER) +#define JAM() \ +{ \ +} +#else +#define JAM() \ +{ \ + char jambuf[20]; \ + sprintf(jambuf, "JAM: PC=$%04X", PC); \ + ASSERT_MSG(jambuf); \ + ADD_CYCLES(2); \ +} +#endif /* NES6502_TESTOPS */ + +#define JMP_INDIRECT() \ +{ \ + temp = bank_readaddress(PC); \ + /* bug in crossing page boundaries */ \ + if (0xFF == (uint8) temp) \ + PC = (bank_readbyte(temp & ~0xFF) << 8) | bank_readbyte(temp); \ + else \ + JUMP(temp); \ + ADD_CYCLES(5); \ +} + +#define JMP_ABSOLUTE() \ +{ \ + JUMP(PC); \ + ADD_CYCLES(3); \ +} + +#define JSR() \ +{ \ + PC++; \ + PUSH(PC >> 8); \ + PUSH(PC & 0xFF); \ + JUMP(PC - 1); \ + ADD_CYCLES(6); \ +} + +/* undocumented */ +#define LAS(cycles, read_func) \ +{ \ + read_func(data); \ + A = X = S = (S & data); \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(cycles); \ +} + +/* undocumented */ +#define LAX(cycles, read_func) \ +{ \ + read_func(A); \ + X = A; \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(cycles); \ +} + +#define LDA(cycles, read_func) \ +{ \ + read_func(A); \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(cycles); \ +} + +#define LDX(cycles, read_func) \ +{ \ + read_func(X); \ + SET_NZ_FLAGS(X);\ + ADD_CYCLES(cycles); \ +} + +#define LDY(cycles, read_func) \ +{ \ + read_func(Y); \ + SET_NZ_FLAGS(Y);\ + ADD_CYCLES(cycles); \ +} + +#define LSR(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr, data); \ + TEST_AND_FLAG(data & 0x01, C_FLAG); \ + data >>= 1; \ + write_func(addr, data); \ + SET_NZ_FLAGS(data); \ + ADD_CYCLES(cycles); \ +} + +#define LSR_A() \ +{ \ + TEST_AND_FLAG(A & 0x01, C_FLAG); \ + A >>= 1; \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(2); \ +} + +/* undocumented */ +#define LXA(cycles, read_func) \ +{ \ + read_func(data); \ + A = X = ((A | 0xEE) & data); \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(cycles); \ +} + +#define NOP() \ +{ \ + ADD_CYCLES(2); \ +} + +#define ORA(cycles, read_func) \ +{ \ + read_func(data); \ + A |= data; \ + SET_NZ_FLAGS(A);\ + ADD_CYCLES(cycles); \ +} + +#define PHA() \ +{ \ + PUSH(A); \ + ADD_CYCLES(3); \ +} + +#define PHP() \ +{ \ + /* B flag is pushed on stack as well */ \ + PUSH((P | B_FLAG)); \ + ADD_CYCLES(3); \ +} + +#define PLA() \ +{ \ + A = PULL(); \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(4); \ +} + +#define PLP() \ +{ \ + P = PULL(); \ + SET_FLAG(R_FLAG); /* ensure reserved flag is set */ \ + ADD_CYCLES(4); \ +} + +/* undocumented */ +#define RLA(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr, data); \ + if (P & C_FLAG) \ + { \ + TEST_AND_FLAG(data & 0x80, C_FLAG); \ + data = (data << 1) | 1; \ + } \ + else \ + { \ + TEST_AND_FLAG(data & 0x80, C_FLAG); \ + data <<= 1; \ + } \ + write_func(addr, data); \ + A &= data; \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(cycles); \ +} + +/* 9-bit rotation (carry flag used for rollover) */ +#define ROL(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr, data); \ + if (P & C_FLAG) \ + { \ + TEST_AND_FLAG(data & 0x80, C_FLAG); \ + data = (data << 1) | 1; \ + } \ + else \ + { \ + TEST_AND_FLAG(data & 0x80, C_FLAG); \ + data <<= 1; \ + } \ + write_func(addr, data); \ + SET_NZ_FLAGS(data); \ + ADD_CYCLES(cycles); \ +} + +#define ROL_A() \ +{ \ + if (P & C_FLAG) \ + { \ + TEST_AND_FLAG(A & 0x80, C_FLAG); \ + A = (A << 1) | 1; \ + } \ + else \ + { \ + TEST_AND_FLAG(A & 0x80, C_FLAG); \ + A <<= 1; \ + } \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(2); \ +} + +#define ROR(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr, data); \ + if (P & C_FLAG) \ + { \ + TEST_AND_FLAG(data & 1, C_FLAG); \ + data = (data >> 1) | 0x80; \ + } \ + else \ + { \ + TEST_AND_FLAG(data & 1, C_FLAG); \ + data >>= 1; \ + } \ + write_func(addr, data); \ + SET_NZ_FLAGS(data); \ + ADD_CYCLES(cycles); \ +} + +#define ROR_A() \ +{ \ + if (P & C_FLAG) \ + { \ + TEST_AND_FLAG(A & 1, C_FLAG); \ + A = (A >> 1) | 0x80; \ + } \ + else \ + { \ + TEST_AND_FLAG(A & 1, C_FLAG); \ + A >>= 1; \ + } \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(2); \ +} + +/* undocumented */ +#define RRA(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr, data); \ + if (P & C_FLAG) \ + { \ + TEST_AND_FLAG(data & 1, C_FLAG); \ + data = (data >> 1) | 0x80; \ + } \ + else \ + { \ + TEST_AND_FLAG(data & 1, C_FLAG); \ + data >>= 1; \ + } \ + write_func(addr, data); \ + ADC(cycles, NO_READ); \ +} + +#define RTI() \ +{ \ + P = PULL(); \ + SET_FLAG(R_FLAG); /* ensure reserved flag is set */ \ + PC = PULL(); \ + PC |= PULL() << 8; \ + ADD_CYCLES(6); \ +} + +#define RTS() \ +{ \ + PC = PULL(); \ + PC = (PC | (PULL() << 8)) + 1; \ + ADD_CYCLES(6); \ +} + +/* undocumented */ +#define SAX(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr); \ + data = A & X; \ + write_func(addr, data); \ + ADD_CYCLES(cycles); \ +} + +/* Warning! NES CPU has no decimal mode, so by default this does no BCD! */ +#ifdef NES6502_DECIMAL +#define SBC(cycles, read_func) \ +{ \ + read_func(data); \ + /* NOT(C) is considered borrow */ \ + temp = A - data - ((P & C_FLAG) ^ C_FLAG); \ + if (P & D_FLAG) \ + { \ + uint8 al, ah; \ + al = (A & 0x0F) - (data & 0x0F) - ((P & C_FLAG) ^ C_FLAG); \ + ah = (A >> 4) - (data >> 4); \ + if (al & 0x10) \ + { \ + al -= 6; \ + ah--; \ + } \ + if (ah & 0x10) \ + ah -= 6; \ + TEST_AND_FLAG(temp < 0x100, C_FLAG); \ + TEST_AND_FLAG(((A ^ temp) & 0x80) && ((A ^ data) & 0x80), V_FLAG); \ + SET_NZ_FLAGS(temp & 0xFF); \ + A = (ah << 4) | (al & 0x0F); \ + } \ + else \ + { \ + TEST_AND_FLAG(((A ^ temp) & 0x80) && ((A ^ data) & 0x80), V_FLAG); \ + TEST_AND_FLAG(temp < 0x100, C_FLAG); \ + A = (uint8) temp; \ + SET_NZ_FLAGS(A & 0xFF); \ + } \ + ADD_CYCLES(cycles); \ +} +#else +#define SBC(cycles, read_func) \ +{ \ + read_func(data); \ + temp = A - data - ((P & C_FLAG) ^ C_FLAG); \ + TEST_AND_FLAG(((A ^ data) & (A ^ temp) & 0x80), V_FLAG); \ + TEST_AND_FLAG(temp < 0x100, C_FLAG); \ + A = (uint8) temp; \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(cycles); \ +} +#endif /* NES6502_DECIMAL */ + +/* undocumented */ +#define SBX(cycles, read_func) \ +{ \ + read_func(data); \ + temp = (A & X) - data; \ + TEST_AND_FLAG(temp < 0x100, C_FLAG); \ + X = temp & 0xFF; \ + SET_NZ_FLAGS(X); \ + ADD_CYCLES(cycles); \ +} + +#define SEC() \ +{ \ + SET_FLAG(C_FLAG); \ + ADD_CYCLES(2); \ +} + +#define SED() \ +{ \ + SET_FLAG(D_FLAG); \ + ADD_CYCLES(2); \ +} + +#define SEI() \ +{ \ + SET_FLAG(I_FLAG); \ + ADD_CYCLES(2); \ +} + +/* undocumented */ +#define SHA(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr); \ + data = A & X & ((uint8) ((addr >> 8) + 1)); \ + write_func(addr, data); \ + ADD_CYCLES(cycles); \ +} + +/* undocumented */ +#define SHS(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr); \ + S = A & X; \ + data = S & ((uint8) ((addr >> 8) + 1)); \ + write_func(addr, data); \ + ADD_CYCLES(cycles); \ +} + +/* undocumented */ +#define SHX(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr); \ + data = X & ((uint8) ((addr >> 8) + 1)); \ + write_func(addr, data); \ + ADD_CYCLES(cycles); \ +} + +/* undocumented */ +#define SHY(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr); \ + data = Y & ((uint8) ((addr >> 8 ) + 1)); \ + write_func(addr, data); \ + ADD_CYCLES(cycles); \ +} + +/* undocumented */ +#define SLO(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr, data); \ + TEST_AND_FLAG(data & 0x80, C_FLAG); \ + data <<= 1; \ + write_func(addr, data); \ + A |= data; \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(cycles); \ +} + +/* unoffical */ +#define SRE(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr, data); \ + TEST_AND_FLAG(data & 1, C_FLAG); \ + data >>= 1; \ + write_func(addr, data); \ + A ^= data; \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(cycles); \ +} + +#define STA(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr); \ + write_func(addr, A); \ + ADD_CYCLES(cycles); \ +} + +#define STX(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr); \ + write_func(addr, X); \ + ADD_CYCLES(cycles); \ +} + +#define STY(cycles, read_func, write_func, addr) \ +{ \ + read_func(addr); \ + write_func(addr, Y); \ + ADD_CYCLES(cycles); \ +} + +#define TAX() \ +{ \ + X = A; \ + SET_NZ_FLAGS(X);\ + ADD_CYCLES(2); \ +} + +#define TAY() \ +{ \ + Y = A; \ + SET_NZ_FLAGS(Y);\ + ADD_CYCLES(2); \ +} + +/* undocumented (triple-NOP) */ +#define TOP() \ +{ \ + PC += 2; \ + ADD_CYCLES(4); \ +} + +#define TSX() \ +{ \ + X = S; \ + SET_NZ_FLAGS(X);\ + ADD_CYCLES(2); \ +} + +#define TXA() \ +{ \ + A = X; \ + SET_NZ_FLAGS(A);\ + ADD_CYCLES(2); \ +} + +#define TXS() \ +{ \ + S = X; \ + ADD_CYCLES(2); \ +} + +#define TYA() \ +{ \ + A = Y; \ + SET_NZ_FLAGS(A); \ + ADD_CYCLES(2); \ +} + + +/* +** Stack and data fetching macros +*/ + +/* Set/clear/test bits in the flags register */ +#define SET_FLAG(mask) P |= (mask) +#define CLEAR_FLAG(mask) P &= ~(mask) +#define IS_FLAG_SET(mask) (P & (mask)) +#define IS_FLAG_CLEAR(mask) (0 == IS_FLAG_SET((mask))) + +#define TEST_AND_FLAG(test, mask) \ +{ \ + if ((test)) \ + SET_FLAG((mask)); \ + else \ + CLEAR_FLAG((mask)); \ +} + + +/* +** flag register helper macros +*/ + +/* register push/pull */ +#define PUSH(value) stack_page[S--] = (uint8) (value) +#define PULL() stack_page[++S] + +/* Sets the Z and N flags based on given data, taken from precomputed table */ +#define SET_NZ_FLAGS(value) P &= ~(N_FLAG | Z_FLAG); \ + P |= flag_table[(value)] + +#define GET_GLOBAL_REGS() \ +{ \ + PC = reg_PC; \ + A = reg_A; \ + X = reg_X; \ + Y = reg_Y; \ + P = reg_P; \ + S = reg_S; \ +} + +#define SET_LOCAL_REGS() \ +{ \ + reg_PC = PC; \ + reg_A = A; \ + reg_X = X; \ + reg_Y = Y; \ + reg_P = P; \ + reg_S = S; \ +} + + +/* static data */ +static nes6502_memread *pmem_read, *pmr; +static nes6502_memwrite *pmem_write, *pmw; + +/* lookup table for N/Z flags */ +static uint8 flag_table[256]; + +/* internal critical CPU vars */ +static uint32 reg_PC; +static uint8 reg_A, reg_P, reg_X, reg_Y, reg_S; +static uint8 int_pending; +static int dma_cycles; + +/* execution cycle count (can be reset by user) */ +static uint32 total_cycles = 0; + +/* memory region pointers */ +static uint8 *nes6502_banks[NES6502_NUMBANKS]; +static uint8 *ram = NULL; +static uint8 *stack_page = NULL; + + +/* +** Zero-page helper macros +*/ + +#define ZP_READ(addr) ram[(addr)] +#define ZP_WRITE(addr, value) ram[(addr)] = (uint8) (value) + + +INLINE uint8 +bank_readbyte (register uint32 address) +{ + ASSERT (nes6502_banks[address >> NES6502_BANKSHIFT]); + return nes6502_banks[address >> NES6502_BANKSHIFT][address & + NES6502_BANKMASK]; +} + +INLINE void +bank_writebyte (register uint32 address, register uint8 value) +{ + ASSERT (nes6502_banks[address >> NES6502_BANKSHIFT]); + nes6502_banks[address >> NES6502_BANKSHIFT][address & NES6502_BANKMASK] = + value; +} + +INLINE uint32 +zp_address (register uint8 address) +{ +#ifdef HOST_LITTLE_ENDIAN + /* TODO: this fails if src address is $xFFF */ + /* TODO: this fails if host architecture doesn't support byte alignment */ + return (uint32) (*(uint16 *) (ram + address)); +#else +#ifdef TARGET_CPU_PPC + return __lhbrx (ram, address); +#else + uint32 x = (uint32) * (uint16 *) (ram + address); + + return (x << 8) | (x >> 8); +#endif /* TARGET_CPU_PPC */ +#endif /* HOST_LITTLE_ENDIAN */ +} + +INLINE uint32 +bank_readaddress (register uint32 address) +{ +#ifdef HOST_LITTLE_ENDIAN + /* TODO: this fails if src address is $xFFF */ + /* TODO: this fails if host architecture doesn't support byte alignment */ + return (uint32) (*(uint16 *) (nes6502_banks[address >> NES6502_BANKSHIFT] + + (address & NES6502_BANKMASK))); +#else +#ifdef TARGET_CPU_PPC + return __lhbrx (nes6502_banks[address >> NES6502_BANKSHIFT], + address & NES6502_BANKMASK); +#else + uint32 x = + (uint32) * (uint16 *) (nes6502_banks[address >> NES6502_BANKSHIFT] + + (address & NES6502_BANKMASK)); + return (x << 8) | (x >> 8); +#endif /* TARGET_CPU_PPC */ +#endif /* HOST_LITTLE_ENDIAN */ +} + +/* read a byte of 6502 memory */ +static uint8 +mem_read (uint32 address) +{ + /* TODO: following cases are N2A03-specific */ + /* RAM */ + if (address < 0x800) + return ram[address]; + /* always paged memory */ + /* else if (address >= 0x6000) */ + else if (address >= 0x8000) + return bank_readbyte (address); + /* check memory range handlers */ + else { + for (pmr = pmem_read; pmr->min_range != 0xFFFFFFFF; pmr++) { + if ((address >= pmr->min_range) && (address <= pmr->max_range)) + return pmr->read_func (address); + } + } + + /* return paged memory */ + return bank_readbyte (address); +} + +/* write a byte of data to 6502 memory */ +static void +mem_write (uint32 address, uint8 value) +{ + /* RAM */ + if (address < 0x800) { + ram[address] = value; + return; + } + /* check memory range handlers */ + else { + for (pmw = pmem_write; pmw->min_range != 0xFFFFFFFF; pmw++) { + if ((address >= pmw->min_range) && (address <= pmw->max_range)) { + pmw->write_func (address, value); + return; + } + } + } + + /* write to paged memory */ + bank_writebyte (address, value); +} + +/* set the current context */ +void +nes6502_setcontext (nes6502_context * cpu) +{ + int loop; + + ASSERT (cpu); + + /* Set the page pointers */ + for (loop = 0; loop < NES6502_NUMBANKS; loop++) + nes6502_banks[loop] = cpu->mem_page[loop]; + + ram = nes6502_banks[0]; /* quicker zero-page/RAM references */ + stack_page = ram + STACK_OFFSET; + + pmem_read = cpu->read_handler; + pmem_write = cpu->write_handler; + + reg_PC = cpu->pc_reg; + reg_A = cpu->a_reg; + reg_P = cpu->p_reg; + reg_X = cpu->x_reg; + reg_Y = cpu->y_reg; + reg_S = cpu->s_reg; + int_pending = cpu->int_pending; + dma_cycles = cpu->dma_cycles; +} + +/* get the current context */ +void +nes6502_getcontext (nes6502_context * cpu) +{ + int loop; + + /* Set the page pointers */ + for (loop = 0; loop < NES6502_NUMBANKS; loop++) + cpu->mem_page[loop] = nes6502_banks[loop]; + + cpu->read_handler = pmem_read; + cpu->write_handler = pmem_write; + + cpu->pc_reg = reg_PC; + cpu->a_reg = reg_A; + cpu->p_reg = reg_P; + cpu->x_reg = reg_X; + cpu->y_reg = reg_Y; + cpu->s_reg = reg_S; + cpu->int_pending = int_pending; + cpu->dma_cycles = dma_cycles; +} + +/* DMA a byte of data from ROM */ +uint8 +nes6502_getbyte (uint32 address) +{ + return bank_readbyte (address); +} + +/* get number of elapsed cycles */ +uint32 +nes6502_getcycles (boolean reset_flag) +{ + uint32 cycles = total_cycles; + + if (reset_flag) + total_cycles = 0; + + return cycles; +} + + +/* Execute instructions until count expires +** +** Returns the number of cycles *actually* executed +** (note that this can be from 0-6 cycles more than you wanted) +*/ +int +nes6502_execute (int remaining_cycles) +{ + int instruction_cycles, old_cycles = total_cycles; + uint32 temp, addr; /* for macros */ + uint32 PC; + uint8 A, X, Y, P, S; + uint8 opcode, data; + uint8 btemp, baddr; /* for macros */ + + GET_GLOBAL_REGS (); + + /* Continue until we run out of cycles */ + while (remaining_cycles > 0) { + instruction_cycles = 0; + + /* check for DMA cycle burning */ + if (dma_cycles) { + if (remaining_cycles <= dma_cycles) { + dma_cycles -= remaining_cycles; + total_cycles += remaining_cycles; + goto _execute_done; + } else { + remaining_cycles -= dma_cycles; + total_cycles += dma_cycles; + dma_cycles = 0; + } + } + + if (int_pending) { + /* NMI has highest priority */ + if (int_pending & NMI_MASK) { + NMI (); + } + /* IRQ has lowest priority */ + else { /* if (int_pending & IRQ_MASK) */ + + if (IS_FLAG_CLEAR (I_FLAG)) + IRQ (); + } + } + + /* Fetch instruction */ + /* nes6502_disasm(PC, P, A, X, Y, S); */ + + opcode = bank_readbyte (PC++); + + /* Execute instruction */ + switch (opcode) { + case 0x00: /* BRK */ + BRK (); + break; + + case 0x01: /* ORA ($nn,X) */ + ORA (6, INDIR_X_BYTE); + break; + + /* JAM */ + case 0x02: /* JAM */ + case 0x12: /* JAM */ + case 0x22: /* JAM */ + case 0x32: /* JAM */ + case 0x42: /* JAM */ + case 0x52: /* JAM */ + case 0x62: /* JAM */ + case 0x72: /* JAM */ + case 0x92: /* JAM */ + case 0xB2: /* JAM */ + case 0xD2: /* JAM */ + case 0xF2: /* JAM */ + JAM (); + /* kill switch for CPU emulation */ + goto _execute_done; + + case 0x03: /* SLO ($nn,X) */ + SLO (8, INDIR_X, mem_write, addr); + break; + + case 0x04: /* NOP $nn */ + case 0x44: /* NOP $nn */ + case 0x64: /* NOP $nn */ + DOP (3); + break; + + case 0x05: /* ORA $nn */ + ORA (3, ZERO_PAGE_BYTE); + break; + + case 0x06: /* ASL $nn */ + ASL (5, ZERO_PAGE, ZP_WRITE, baddr); + break; + + case 0x07: /* SLO $nn */ + SLO (5, ZERO_PAGE, ZP_WRITE, baddr); + break; + + case 0x08: /* PHP */ + PHP (); + break; + + case 0x09: /* ORA #$nn */ + ORA (2, IMMEDIATE_BYTE); + break; + + case 0x0A: /* ASL A */ + ASL_A (); + break; + + case 0x0B: /* ANC #$nn */ + ANC (2, IMMEDIATE_BYTE); + break; + + case 0x0C: /* NOP $nnnn */ + TOP (); + break; + + case 0x0D: /* ORA $nnnn */ + ORA (4, ABSOLUTE_BYTE); + break; + + case 0x0E: /* ASL $nnnn */ + ASL (6, ABSOLUTE, mem_write, addr); + break; + + case 0x0F: /* SLO $nnnn */ + SLO (6, ABSOLUTE, mem_write, addr); + break; + + case 0x10: /* BPL $nnnn */ + BPL (); + break; + + case 0x11: /* ORA ($nn),Y */ + ORA (5, INDIR_Y_BYTE); + break; + + case 0x13: /* SLO ($nn),Y */ + SLO (8, INDIR_Y, mem_write, addr); + break; + + case 0x14: /* NOP $nn,X */ + case 0x34: /* NOP */ + case 0x54: /* NOP $nn,X */ + case 0x74: /* NOP $nn,X */ + case 0xD4: /* NOP $nn,X */ + case 0xF4: /* NOP ($nn,X) */ + DOP (4); + break; + + case 0x15: /* ORA $nn,X */ + ORA (4, ZP_IND_X_BYTE); + break; + + case 0x16: /* ASL $nn,X */ + ASL (6, ZP_IND_X, ZP_WRITE, baddr); + break; + + case 0x17: /* SLO $nn,X */ + SLO (6, ZP_IND_X, ZP_WRITE, baddr); + break; + + case 0x18: /* CLC */ + CLC (); + break; + + case 0x19: /* ORA $nnnn,Y */ + ORA (4, ABS_IND_Y_BYTE); + break; + + case 0x1A: /* NOP */ + case 0x3A: /* NOP */ + case 0x5A: /* NOP */ + case 0x7A: /* NOP */ + case 0xDA: /* NOP */ + case 0xFA: /* NOP */ + NOP (); + break; + + case 0x1B: /* SLO $nnnn,Y */ + SLO (7, ABS_IND_Y, mem_write, addr); + break; + + case 0x1C: /* NOP $nnnn,X */ + case 0x3C: /* NOP $nnnn,X */ + case 0x5C: /* NOP $nnnn,X */ + case 0x7C: /* NOP $nnnn,X */ + case 0xDC: /* NOP $nnnn,X */ + case 0xFC: /* NOP $nnnn,X */ + TOP (); + break; + + case 0x1D: /* ORA $nnnn,X */ + ORA (4, ABS_IND_X_BYTE); + break; + + case 0x1E: /* ASL $nnnn,X */ + ASL (7, ABS_IND_X, mem_write, addr); + break; + + case 0x1F: /* SLO $nnnn,X */ + SLO (7, ABS_IND_X, mem_write, addr); + break; + + case 0x20: /* JSR $nnnn */ + JSR (); + break; + + case 0x21: /* AND ($nn,X) */ + AND (6, INDIR_X_BYTE); + break; + + case 0x23: /* RLA ($nn,X) */ + RLA (8, INDIR_X, mem_write, addr); + break; + + case 0x24: /* BIT $nn */ + BIT (3, ZERO_PAGE_BYTE); + break; + + case 0x25: /* AND $nn */ + AND (3, ZERO_PAGE_BYTE); + break; + + case 0x26: /* ROL $nn */ + ROL (5, ZERO_PAGE, ZP_WRITE, baddr); + break; + + case 0x27: /* RLA $nn */ + RLA (5, ZERO_PAGE, ZP_WRITE, baddr); + break; + + case 0x28: /* PLP */ + PLP (); + break; + + case 0x29: /* AND #$nn */ + AND (2, IMMEDIATE_BYTE); + break; + + case 0x2A: /* ROL A */ + ROL_A (); + break; + + case 0x2B: /* ANC #$nn */ + ANC (2, IMMEDIATE_BYTE); + break; + + case 0x2C: /* BIT $nnnn */ + BIT (4, ABSOLUTE_BYTE); + break; + + case 0x2D: /* AND $nnnn */ + AND (4, ABSOLUTE_BYTE); + break; + + case 0x2E: /* ROL $nnnn */ + ROL (6, ABSOLUTE, mem_write, addr); + break; + + case 0x2F: /* RLA $nnnn */ + RLA (6, ABSOLUTE, mem_write, addr); + break; + + case 0x30: /* BMI $nnnn */ + BMI (); + break; + + case 0x31: /* AND ($nn),Y */ + AND (5, INDIR_Y_BYTE); + break; + + case 0x33: /* RLA ($nn),Y */ + RLA (8, INDIR_Y, mem_write, addr); + break; + + case 0x35: /* AND $nn,X */ + AND (4, ZP_IND_X_BYTE); + break; + + case 0x36: /* ROL $nn,X */ + ROL (6, ZP_IND_X, ZP_WRITE, baddr); + break; + + case 0x37: /* RLA $nn,X */ + RLA (6, ZP_IND_X, ZP_WRITE, baddr); + break; + + case 0x38: /* SEC */ + SEC (); + break; + + case 0x39: /* AND $nnnn,Y */ + AND (4, ABS_IND_Y_BYTE); + break; + + case 0x3B: /* RLA $nnnn,Y */ + RLA (7, ABS_IND_Y, mem_write, addr); + break; + + case 0x3D: /* AND $nnnn,X */ + AND (4, ABS_IND_X_BYTE); + break; + + case 0x3E: /* ROL $nnnn,X */ + ROL (7, ABS_IND_X, mem_write, addr); + break; + + case 0x3F: /* RLA $nnnn,X */ + RLA (7, ABS_IND_X, mem_write, addr); + break; + + case 0x40: /* RTI */ + RTI (); + break; + + case 0x41: /* EOR ($nn,X) */ + EOR (6, INDIR_X_BYTE); + break; + + case 0x43: /* SRE ($nn,X) */ + SRE (8, INDIR_X, mem_write, addr); + break; + + case 0x45: /* EOR $nn */ + EOR (3, ZERO_PAGE_BYTE); + break; + + case 0x46: /* LSR $nn */ + LSR (5, ZERO_PAGE, ZP_WRITE, baddr); + break; + + case 0x47: /* SRE $nn */ + SRE (5, ZERO_PAGE, ZP_WRITE, baddr); + break; + + case 0x48: /* PHA */ + PHA (); + break; + + case 0x49: /* EOR #$nn */ + EOR (2, IMMEDIATE_BYTE); + break; + + case 0x4A: /* LSR A */ + LSR_A (); + break; + + case 0x4B: /* ASR #$nn */ + ASR (2, IMMEDIATE_BYTE); + break; + + case 0x4C: /* JMP $nnnn */ + JMP_ABSOLUTE (); + break; + + case 0x4D: /* EOR $nnnn */ + EOR (4, ABSOLUTE_BYTE); + break; + + case 0x4E: /* LSR $nnnn */ + LSR (6, ABSOLUTE, mem_write, addr); + break; + + case 0x4F: /* SRE $nnnn */ + SRE (6, ABSOLUTE, mem_write, addr); + break; + + case 0x50: /* BVC $nnnn */ + BVC (); + break; + + case 0x51: /* EOR ($nn),Y */ + EOR (5, INDIR_Y_BYTE); + break; + + case 0x53: /* SRE ($nn),Y */ + SRE (8, INDIR_Y, mem_write, addr); + break; + + case 0x55: /* EOR $nn,X */ + EOR (4, ZP_IND_X_BYTE); + break; + + case 0x56: /* LSR $nn,X */ + LSR (6, ZP_IND_X, ZP_WRITE, baddr); + break; + + case 0x57: /* SRE $nn,X */ + SRE (6, ZP_IND_X, ZP_WRITE, baddr); + break; + + case 0x58: /* CLI */ + CLI (); + break; + + case 0x59: /* EOR $nnnn,Y */ + EOR (4, ABS_IND_Y_BYTE); + break; + + case 0x5B: /* SRE $nnnn,Y */ + SRE (7, ABS_IND_Y, mem_write, addr); + break; + + case 0x5D: /* EOR $nnnn,X */ + EOR (4, ABS_IND_X_BYTE); + break; + + case 0x5E: /* LSR $nnnn,X */ + LSR (7, ABS_IND_X, mem_write, addr); + break; + + case 0x5F: /* SRE $nnnn,X */ + SRE (7, ABS_IND_X, mem_write, addr); + break; + + case 0x60: /* RTS */ + RTS (); + break; + + case 0x61: /* ADC ($nn,X) */ + ADC (6, INDIR_X_BYTE); + break; + + case 0x63: /* RRA ($nn,X) */ + RRA (8, INDIR_X, mem_write, addr); + break; + + case 0x65: /* ADC $nn */ + ADC (3, ZERO_PAGE_BYTE); + break; + + case 0x66: /* ROR $nn */ + ROR (5, ZERO_PAGE, ZP_WRITE, baddr); + break; + + case 0x67: /* RRA $nn */ + RRA (5, ZERO_PAGE, ZP_WRITE, baddr); + break; + + case 0x68: /* PLA */ + PLA (); + break; + + case 0x69: /* ADC #$nn */ + ADC (2, IMMEDIATE_BYTE); + break; + + case 0x6A: /* ROR A */ + ROR_A (); + break; + + case 0x6B: /* ARR #$nn */ + ARR (2, IMMEDIATE_BYTE); + break; + + case 0x6C: /* JMP ($nnnn) */ + JMP_INDIRECT (); + break; + + case 0x6D: /* ADC $nnnn */ + ADC (4, ABSOLUTE_BYTE); + break; + + case 0x6E: /* ROR $nnnn */ + ROR (6, ABSOLUTE, mem_write, addr); + break; + + case 0x6F: /* RRA $nnnn */ + RRA (6, ABSOLUTE, mem_write, addr); + break; + + case 0x70: /* BVS $nnnn */ + BVS (); + break; + + case 0x71: /* ADC ($nn),Y */ + ADC (5, INDIR_Y_BYTE); + break; + + case 0x73: /* RRA ($nn),Y */ + RRA (8, INDIR_Y, mem_write, addr); + break; + + case 0x75: /* ADC $nn,X */ + ADC (4, ZP_IND_X_BYTE); + break; + + case 0x76: /* ROR $nn,X */ + ROR (6, ZP_IND_X, ZP_WRITE, baddr); + break; + + case 0x77: /* RRA $nn,X */ + RRA (6, ZP_IND_X, ZP_WRITE, baddr); + break; + + case 0x78: /* SEI */ + SEI (); + break; + + case 0x79: /* ADC $nnnn,Y */ + ADC (4, ABS_IND_Y_BYTE); + break; + + case 0x7B: /* RRA $nnnn,Y */ + RRA (7, ABS_IND_Y, mem_write, addr); + break; + + case 0x7D: /* ADC $nnnn,X */ + ADC (4, ABS_IND_X_BYTE); + break; + + case 0x7E: /* ROR $nnnn,X */ + ROR (7, ABS_IND_X, mem_write, addr); + break; + + case 0x7F: /* RRA $nnnn,X */ + RRA (7, ABS_IND_X, mem_write, addr); + break; + + case 0x80: /* NOP #$nn */ + case 0x82: /* NOP #$nn */ + case 0x89: /* NOP #$nn */ + case 0xC2: /* NOP #$nn */ + case 0xE2: /* NOP #$nn */ + DOP (2); + break; + + case 0x81: /* STA ($nn,X) */ + STA (6, INDIR_X_ADDR, mem_write, addr); + break; + + case 0x83: /* SAX ($nn,X) */ + SAX (6, INDIR_X_ADDR, mem_write, addr); + break; + + case 0x84: /* STY $nn */ + STY (3, ZERO_PAGE_ADDR, ZP_WRITE, baddr); + break; + + case 0x85: /* STA $nn */ + STA (3, ZERO_PAGE_ADDR, ZP_WRITE, baddr); + break; + + case 0x86: /* STX $nn */ + STX (3, ZERO_PAGE_ADDR, ZP_WRITE, baddr); + break; + + case 0x87: /* SAX $nn */ + SAX (3, ZERO_PAGE_ADDR, ZP_WRITE, baddr); + break; + + case 0x88: /* DEY */ + DEY (); + break; + + case 0x8A: /* TXA */ + TXA (); + break; + + case 0x8B: /* ANE #$nn */ + ANE (2, IMMEDIATE_BYTE); + break; + + case 0x8C: /* STY $nnnn */ + STY (4, ABSOLUTE_ADDR, mem_write, addr); + break; + + case 0x8D: /* STA $nnnn */ + STA (4, ABSOLUTE_ADDR, mem_write, addr); + break; + + case 0x8E: /* STX $nnnn */ + STX (4, ABSOLUTE_ADDR, mem_write, addr); + break; + + case 0x8F: /* SAX $nnnn */ + SAX (4, ABSOLUTE_ADDR, mem_write, addr); + break; + + case 0x90: /* BCC $nnnn */ + BCC (); + break; + + case 0x91: /* STA ($nn),Y */ + STA (6, INDIR_Y_ADDR, mem_write, addr); + break; + + case 0x93: /* SHA ($nn),Y */ + SHA (6, INDIR_Y_ADDR, mem_write, addr); + break; + + case 0x94: /* STY $nn,X */ + STY (4, ZP_IND_X_ADDR, ZP_WRITE, baddr); + break; + + case 0x95: /* STA $nn,X */ + STA (4, ZP_IND_X_ADDR, ZP_WRITE, baddr); + break; + + case 0x96: /* STX $nn,Y */ + STX (4, ZP_IND_Y_ADDR, ZP_WRITE, baddr); + break; + + case 0x97: /* SAX $nn,Y */ + SAX (4, ZP_IND_Y_ADDR, ZP_WRITE, baddr); + break; + + case 0x98: /* TYA */ + TYA (); + break; + + case 0x99: /* STA $nnnn,Y */ + STA (5, ABS_IND_Y_ADDR, mem_write, addr); + break; + + case 0x9A: /* TXS */ + TXS (); + break; + + case 0x9B: /* SHS $nnnn,Y */ + SHS (5, ABS_IND_Y_ADDR, mem_write, addr); + break; + + case 0x9C: /* SHY $nnnn,X */ + SHY (5, ABS_IND_X_ADDR, mem_write, addr); + break; + + case 0x9D: /* STA $nnnn,X */ + STA (5, ABS_IND_X_ADDR, mem_write, addr); + break; + + case 0x9E: /* SHX $nnnn,Y */ + SHX (5, ABS_IND_Y_ADDR, mem_write, addr); + break; + + case 0x9F: /* SHA $nnnn,Y */ + SHA (5, ABS_IND_Y_ADDR, mem_write, addr); + break; + + case 0xA0: /* LDY #$nn */ + LDY (2, IMMEDIATE_BYTE); + break; + + case 0xA1: /* LDA ($nn,X) */ + LDA (6, INDIR_X_BYTE); + break; + + case 0xA2: /* LDX #$nn */ + LDX (2, IMMEDIATE_BYTE); + break; + + case 0xA3: /* LAX ($nn,X) */ + LAX (6, INDIR_X_BYTE); + break; + + case 0xA4: /* LDY $nn */ + LDY (3, ZERO_PAGE_BYTE); + break; + + case 0xA5: /* LDA $nn */ + LDA (3, ZERO_PAGE_BYTE); + break; + + case 0xA6: /* LDX $nn */ + LDX (3, ZERO_PAGE_BYTE); + break; + + case 0xA7: /* LAX $nn */ + LAX (3, ZERO_PAGE_BYTE); + break; + + case 0xA8: /* TAY */ + TAY (); + break; + + case 0xA9: /* LDA #$nn */ + LDA (2, IMMEDIATE_BYTE); + break; + + case 0xAA: /* TAX */ + TAX (); + break; + + case 0xAB: /* LXA #$nn */ + LXA (2, IMMEDIATE_BYTE); + break; + + case 0xAC: /* LDY $nnnn */ + LDY (4, ABSOLUTE_BYTE); + break; + + case 0xAD: /* LDA $nnnn */ + LDA (4, ABSOLUTE_BYTE); + break; + + case 0xAE: /* LDX $nnnn */ + LDX (4, ABSOLUTE_BYTE); + break; + + case 0xAF: /* LAX $nnnn */ + LAX (4, ABSOLUTE_BYTE); + break; + + case 0xB0: /* BCS $nnnn */ + BCS (); + break; + + case 0xB1: /* LDA ($nn),Y */ + LDA (5, INDIR_Y_BYTE); + break; + + case 0xB3: /* LAX ($nn),Y */ + LAX (5, INDIR_Y_BYTE); + break; + + case 0xB4: /* LDY $nn,X */ + LDY (4, ZP_IND_X_BYTE); + break; + + case 0xB5: /* LDA $nn,X */ + LDA (4, ZP_IND_X_BYTE); + break; + + case 0xB6: /* LDX $nn,Y */ + LDX (4, ZP_IND_Y_BYTE); + break; + + case 0xB7: /* LAX $nn,Y */ + LAX (4, ZP_IND_Y_BYTE); + break; + + case 0xB8: /* CLV */ + CLV (); + break; + + case 0xB9: /* LDA $nnnn,Y */ + LDA (4, ABS_IND_Y_BYTE); + break; + + case 0xBA: /* TSX */ + TSX (); + break; + + case 0xBB: /* LAS $nnnn,Y */ + LAS (4, ABS_IND_Y_BYTE); + break; + + case 0xBC: /* LDY $nnnn,X */ + LDY (4, ABS_IND_X_BYTE); + break; + + case 0xBD: /* LDA $nnnn,X */ + LDA (4, ABS_IND_X_BYTE); + break; + + case 0xBE: /* LDX $nnnn,Y */ + LDX (4, ABS_IND_Y_BYTE); + break; + + case 0xBF: /* LAX $nnnn,Y */ + LAX (4, ABS_IND_Y_BYTE); + break; + + case 0xC0: /* CPY #$nn */ + CPY (2, IMMEDIATE_BYTE); + break; + + case 0xC1: /* CMP ($nn,X) */ + CMP (6, INDIR_X_BYTE); + break; + + case 0xC3: /* DCP ($nn,X) */ + DCP (8, INDIR_X, mem_write, addr); + break; + + case 0xC4: /* CPY $nn */ + CPY (3, ZERO_PAGE_BYTE); + break; + + case 0xC5: /* CMP $nn */ + CMP (3, ZERO_PAGE_BYTE); + break; + + case 0xC6: /* DEC $nn */ + DEC (5, ZERO_PAGE, ZP_WRITE, baddr); + break; + + case 0xC7: /* DCP $nn */ + DCP (5, ZERO_PAGE, ZP_WRITE, baddr); + break; + + case 0xC8: /* INY */ + INY (); + break; + + case 0xC9: /* CMP #$nn */ + CMP (2, IMMEDIATE_BYTE); + break; + + case 0xCA: /* DEX */ + DEX (); + break; + + case 0xCB: /* SBX #$nn */ + SBX (2, IMMEDIATE_BYTE); + break; + + case 0xCC: /* CPY $nnnn */ + CPY (4, ABSOLUTE_BYTE); + break; + + case 0xCD: /* CMP $nnnn */ + CMP (4, ABSOLUTE_BYTE); + break; + + case 0xCE: /* DEC $nnnn */ + DEC (6, ABSOLUTE, mem_write, addr); + break; + + case 0xCF: /* DCP $nnnn */ + DCP (6, ABSOLUTE, mem_write, addr); + break; + + case 0xD0: /* BNE $nnnn */ + BNE (); + break; + + case 0xD1: /* CMP ($nn),Y */ + CMP (5, INDIR_Y_BYTE); + break; + + case 0xD3: /* DCP ($nn),Y */ + DCP (8, INDIR_Y, mem_write, addr); + break; + + case 0xD5: /* CMP $nn,X */ + CMP (4, ZP_IND_X_BYTE); + break; + + case 0xD6: /* DEC $nn,X */ + DEC (6, ZP_IND_X, ZP_WRITE, baddr); + break; + + case 0xD7: /* DCP $nn,X */ + DCP (6, ZP_IND_X, ZP_WRITE, baddr); + break; + + case 0xD8: /* CLD */ + CLD (); + break; + + case 0xD9: /* CMP $nnnn,Y */ + CMP (4, ABS_IND_Y_BYTE); + break; + + case 0xDB: /* DCP $nnnn,Y */ + DCP (7, ABS_IND_Y, mem_write, addr); + break; + + case 0xDD: /* CMP $nnnn,X */ + CMP (4, ABS_IND_X_BYTE); + break; + + case 0xDE: /* DEC $nnnn,X */ + DEC (7, ABS_IND_X, mem_write, addr); + break; + + case 0xDF: /* DCP $nnnn,X */ + DCP (7, ABS_IND_X, mem_write, addr); + break; + + case 0xE0: /* CPX #$nn */ + CPX (2, IMMEDIATE_BYTE); + break; + + case 0xE1: /* SBC ($nn,X) */ + SBC (6, INDIR_X_BYTE); + break; + + case 0xE3: /* ISB ($nn,X) */ + ISB (8, INDIR_X, mem_write, addr); + break; + + case 0xE4: /* CPX $nn */ + CPX (3, ZERO_PAGE_BYTE); + break; + + case 0xE5: /* SBC $nn */ + SBC (3, ZERO_PAGE_BYTE); + break; + + case 0xE6: /* INC $nn */ + INC (5, ZERO_PAGE, ZP_WRITE, baddr); + break; + + case 0xE7: /* ISB $nn */ + ISB (5, ZERO_PAGE, ZP_WRITE, baddr); + break; + + case 0xE8: /* INX */ + INX (); + break; + + case 0xE9: /* SBC #$nn */ + case 0xEB: /* USBC #$nn */ + SBC (2, IMMEDIATE_BYTE); + break; + + case 0xEA: /* NOP */ + NOP (); + break; + + case 0xEC: /* CPX $nnnn */ + CPX (4, ABSOLUTE_BYTE); + break; + + case 0xED: /* SBC $nnnn */ + SBC (4, ABSOLUTE_BYTE); + break; + + case 0xEE: /* INC $nnnn */ + INC (6, ABSOLUTE, mem_write, addr); + break; + + case 0xEF: /* ISB $nnnn */ + ISB (6, ABSOLUTE, mem_write, addr); + break; + + case 0xF0: /* BEQ $nnnn */ + BEQ (); + break; + + case 0xF1: /* SBC ($nn),Y */ + SBC (5, INDIR_Y_BYTE); + break; + + case 0xF3: /* ISB ($nn),Y */ + ISB (8, INDIR_Y, mem_write, addr); + break; + + case 0xF5: /* SBC $nn,X */ + SBC (4, ZP_IND_X_BYTE); + break; + + case 0xF6: /* INC $nn,X */ + INC (6, ZP_IND_X, ZP_WRITE, baddr); + break; + + case 0xF7: /* ISB $nn,X */ + ISB (6, ZP_IND_X, ZP_WRITE, baddr); + break; + + case 0xF8: /* SED */ + SED (); + break; + + case 0xF9: /* SBC $nnnn,Y */ + SBC (4, ABS_IND_Y_BYTE); + break; + + case 0xFB: /* ISB $nnnn,Y */ + ISB (7, ABS_IND_Y, mem_write, addr); + break; + + case 0xFD: /* SBC $nnnn,X */ + SBC (4, ABS_IND_X_BYTE); + break; + + case 0xFE: /* INC $nnnn,X */ + INC (7, ABS_IND_X, mem_write, addr); + break; + + case 0xFF: /* ISB $nnnn,X */ + ISB (7, ABS_IND_X, mem_write, addr); + break; + } + + /* Calculate remaining/elapsed clock cycles */ + remaining_cycles -= instruction_cycles; + total_cycles += instruction_cycles; + } + +_execute_done: + + /* restore local copy of regs */ + SET_LOCAL_REGS (); + + /* Return our actual amount of executed cycles */ + return (total_cycles - old_cycles); +} + +/* Initialize tables, etc. */ +void +nes6502_init (void) +{ + int index; + + /* Build the N / Z flag lookup table */ + flag_table[0] = Z_FLAG; + + for (index = 1; index < 256; index++) + flag_table[index] = (index & 0x80) ? N_FLAG : 0; + + reg_A = reg_X = reg_Y = 0; + reg_S = 0xFF; /* Stack grows down */ +} + + +/* Issue a CPU Reset */ +void +nes6502_reset (void) +{ + reg_P = Z_FLAG | R_FLAG | I_FLAG; /* Reserved bit always 1 */ + int_pending = dma_cycles = 0; /* No pending interrupts */ + reg_PC = bank_readaddress (RESET_VECTOR); /* Fetch reset vector */ + /* TODO: 6 cycles for RESET? */ +} + +/* Non-maskable interrupt */ +void +nes6502_nmi (void) +{ + int_pending |= NMI_MASK; +} + +/* Interrupt request */ +void +nes6502_irq (void) +{ + int_pending |= IRQ_MASK; +} + +/* Set dma period (in cycles) */ +void +nes6502_setdma (int cycles) +{ + dma_cycles += cycles; +} + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.6 2000/07/04 04:50:07 matt +** minor change to includes +** +** Revision 1.5 2000/07/03 02:18:16 matt +** added a few notes about potential failure cases +** +** Revision 1.4 2000/06/09 15:12:25 matt +** initial revision +** +*/ diff --git a/gst/nsf/nes6502.h b/gst/nsf/nes6502.h new file mode 100644 index 00000000..e8ccffd4 --- /dev/null +++ b/gst/nsf/nes6502.h @@ -0,0 +1,160 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** nes6502.h +** +** NES custom 6502 CPU definitions / prototypes +** $Id$ +*/ + +/* NOTE: 16-bit addresses avoided like the plague: use 32-bit values +** wherever humanly possible +*/ +#ifndef _NES6502_H_ +#define _NES6502_H_ + +/* Define this to enable decimal mode in ADC / SBC (not needed in NES) */ +/*#define NES6502_DECIMAL*/ + +/* number of bank pointers the CPU emulation core handles */ +#ifdef NSF_PLAYER +#define NES6502_4KBANKS +#endif + +#ifdef NES6502_4KBANKS +#define NES6502_NUMBANKS 16 +#define NES6502_BANKSHIFT 12 +#else +#define NES6502_NUMBANKS 8 +#define NES6502_BANKSHIFT 13 +#endif + +#define NES6502_BANKMASK ((0x10000 / NES6502_NUMBANKS) - 1) + + +/* P (flag) register bitmasks */ +#define N_FLAG 0x80 +#define V_FLAG 0x40 +#define R_FLAG 0x20 /* Reserved, always 1 */ +#define B_FLAG 0x10 +#define D_FLAG 0x08 +#define I_FLAG 0x04 +#define Z_FLAG 0x02 +#define C_FLAG 0x01 + +/* Vector addresses */ +#define NMI_VECTOR 0xFFFA +#define RESET_VECTOR 0xFFFC +#define IRQ_VECTOR 0xFFFE + +/* cycle counts for interrupts */ +#define INT_CYCLES 7 +#define RESET_CYCLES 6 + +#define NMI_MASK 0x01 +#define IRQ_MASK 0x02 + +/* Stack is located on 6502 page 1 */ +#define STACK_OFFSET 0x0100 + +typedef struct +{ + uint32 min_range, max_range; + uint8 (*read_func)(uint32 address); +} nes6502_memread; + +typedef struct +{ + uint32 min_range, max_range; + void (*write_func)(uint32 address, uint8 value); +} nes6502_memwrite; + +typedef struct +{ + uint8 *mem_page[NES6502_NUMBANKS]; /* memory page pointers */ + nes6502_memread *read_handler; + nes6502_memwrite *write_handler; + int dma_cycles; + uint32 pc_reg; + uint8 a_reg, p_reg, x_reg, y_reg, s_reg; + uint8 int_pending; +} nes6502_context; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Functions which govern the 6502's execution */ +extern void nes6502_init(void); +extern void nes6502_reset(void); +extern int nes6502_execute(int total_cycles); +extern void nes6502_nmi(void); +extern void nes6502_irq(void); +extern uint8 nes6502_getbyte(uint32 address); +extern uint32 nes6502_getcycles(boolean reset_flag); +extern void nes6502_setdma(int cycles); + +/* Context get/set */ +extern void nes6502_setcontext(nes6502_context *cpu); +extern void nes6502_getcontext(nes6502_context *cpu); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _NES6502_H_ */ + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.4 2000/06/09 15:12:25 matt +** initial revision +** +*/ diff --git a/gst/nsf/nes_apu.c b/gst/nsf/nes_apu.c new file mode 100644 index 00000000..0e2c9520 --- /dev/null +++ b/gst/nsf/nes_apu.c @@ -0,0 +1,1246 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** nes_apu.c +** +** NES APU emulation +** $Id$ +*/ + +#include <string.h> +#include "types.h" +#include "log.h" +#include "nes_apu.h" +#include "nes6502.h" + +#ifdef NSF_PLAYER +#include "nsf.h" +#else +#include "nes.h" +#include "nes_ppu.h" +#include "nes_mmc.h" +#include "nesinput.h" +#endif /* !NSF_PLAYER */ + + +#define APU_OVERSAMPLE +#define APU_VOLUME_DECAY(x) ((x) -= ((x) >> 7)) + + +/* pointer to active APU */ +static apu_t *apu; + +/* look up table madness */ +static int32 decay_lut[16]; +static int vbl_lut[32]; +static int trilength_lut[128]; + +/* noise lookups for both modes */ +#ifndef REALTIME_NOISE +static int8 noise_long_lut[APU_NOISE_32K]; +static int8 noise_short_lut[APU_NOISE_93]; +#endif /* !REALTIME_NOISE */ + + +/* vblank length table used for rectangles, triangle, noise */ +static const uint8 vbl_length[32] = { + 5, 127, + 10, 1, + 19, 2, + 40, 3, + 80, 4, + 30, 5, + 7, 6, + 13, 7, + 6, 8, + 12, 9, + 24, 10, + 48, 11, + 96, 12, + 36, 13, + 8, 14, + 16, 15 +}; + +/* frequency limit of rectangle channels */ +static const int freq_limit[8] = { + 0x3FF, 0x555, 0x666, 0x71C, 0x787, 0x7C1, 0x7E0, 0x7F0 +}; + +/* noise frequency lookup table */ +static const int noise_freq[16] = { + 4, 8, 16, 32, 64, 96, 128, 160, + 202, 254, 380, 508, 762, 1016, 2034, 4068 +}; + +/* DMC transfer freqs */ +const int dmc_clocks[16] = { + 428, 380, 340, 320, 286, 254, 226, 214, + 190, 160, 142, 128, 106, 85, 72, 54 +}; + +/* ratios of pos/neg pulse for rectangle waves */ +static const int duty_lut[4] = { 2, 4, 8, 12 }; + + +void +apu_setcontext (apu_t * src_apu) +{ + apu = src_apu; +} + + +/* +** Simple queue routines +*/ +#define APU_QEMPTY() (apu->q_head == apu->q_tail) + +static void +apu_enqueue (apudata_t * d) +{ + ASSERT (apu); + apu->queue[apu->q_head] = *d; + + apu->q_head = (apu->q_head + 1) & APUQUEUE_MASK; + + if (APU_QEMPTY ()) + log_printf ("apu: queue overflow\n"); +} + +static apudata_t * +apu_dequeue (void) +{ + int loc; + + ASSERT (apu); + + if (APU_QEMPTY ()) + log_printf ("apu: queue empty\n"); + + loc = apu->q_tail; + apu->q_tail = (apu->q_tail + 1) & APUQUEUE_MASK; + + return &apu->queue[loc]; +} + +void +apu_setchan (int chan, boolean enabled) +{ + ASSERT (apu); + apu->mix_enable[chan] = enabled; +} + +/* emulation of the 15-bit shift register the +** NES uses to generate pseudo-random series +** for the white noise channel +*/ +#ifdef REALTIME_NOISE +INLINE int8 +shift_register15 (uint8 xor_tap) +{ + static int sreg = 0x4000; + int bit0, tap, bit14; + + bit0 = sreg & 1; + tap = (sreg & xor_tap) ? 1 : 0; + bit14 = (bit0 ^ tap); + sreg >>= 1; + sreg |= (bit14 << 14); + return (bit0 ^ 1); +} +#else +static void +shift_register15 (int8 * buf, int count) +{ + static int sreg = 0x4000; + int bit0, bit1, bit6, bit14; + + if (count == APU_NOISE_93) { + while (count--) { + bit0 = sreg & 1; + bit6 = (sreg & 0x40) >> 6; + bit14 = (bit0 ^ bit6); + sreg >>= 1; + sreg |= (bit14 << 14); + *buf++ = bit0 ^ 1; + } + } else { /* 32K noise */ + + while (count--) { + bit0 = sreg & 1; + bit1 = (sreg & 2) >> 1; + bit14 = (bit0 ^ bit1); + sreg >>= 1; + sreg |= (bit14 << 14); + *buf++ = bit0 ^ 1; + } + } +} +#endif + +/* RECTANGLE WAVE +** ============== +** reg0: 0-3=volume, 4=envelope, 5=hold, 6-7=duty cycle +** reg1: 0-2=sweep shifts, 3=sweep inc/dec, 4-6=sweep length, 7=sweep on +** reg2: 8 bits of freq +** reg3: 0-2=high freq, 7-4=vbl length counter +*/ +#define APU_RECTANGLE_OUTPUT chan->output_vol +static int32 +apu_rectangle (rectangle_t * chan) +{ + int32 output; + +#ifdef APU_OVERSAMPLE + int num_times; + int32 total; +#endif + + APU_VOLUME_DECAY (chan->output_vol); + + if (FALSE == chan->enabled || 0 == chan->vbl_length) + return APU_RECTANGLE_OUTPUT; + + /* vbl length counter */ + if (FALSE == chan->holdnote) + chan->vbl_length--; + + /* envelope decay at a rate of (env_delay + 1) / 240 secs */ + chan->env_phase -= 4; /* 240/60 */ + while (chan->env_phase < 0) { + chan->env_phase += chan->env_delay; + + if (chan->holdnote) + chan->env_vol = (chan->env_vol + 1) & 0x0F; + else if (chan->env_vol < 0x0F) + chan->env_vol++; + } + + if ((FALSE == chan->sweep_inc && chan->freq > chan->freq_limit) + || chan->freq < APU_TO_FIXED (4)) + return APU_RECTANGLE_OUTPUT; + + /* frequency sweeping at a rate of (sweep_delay + 1) / 120 secs */ + if (chan->sweep_on && chan->sweep_shifts) { + chan->sweep_phase -= 2; /* 120/60 */ + while (chan->sweep_phase < 0) { + chan->sweep_phase += chan->sweep_delay; + if (chan->sweep_inc) /* ramp up */ + chan->freq -= chan->freq >> (chan->sweep_shifts); + else /* ramp down */ + chan->freq += chan->freq >> (chan->sweep_shifts); + } + } + + chan->phaseacc -= apu->cycle_rate; /* # of cycles per sample */ + if (chan->phaseacc >= 0) + return APU_RECTANGLE_OUTPUT; + +#ifdef APU_OVERSAMPLE + num_times = total = 0; + + if (chan->fixed_envelope) + output = chan->volume << 8; /* fixed volume */ + else + output = (chan->env_vol ^ 0x0F) << 8; +#endif + + while (chan->phaseacc < 0) { + chan->phaseacc += chan->freq; + chan->adder = (chan->adder + 1) & 0x0F; + +#ifdef APU_OVERSAMPLE + if (chan->adder < chan->duty_flip) + total += output; + else + total -= output; + + num_times++; +#endif + } + +#ifdef APU_OVERSAMPLE + chan->output_vol = total / num_times; +#else + if (chan->fixed_envelope) + output = chan->volume << 8; /* fixed volume */ + else + output = (chan->env_vol ^ 0x0F) << 8; + + if (0 == chan->adder) + chan->output_vol = output; + else if (chan->adder == chan->duty_flip) + chan->output_vol = -output; +#endif + + return APU_RECTANGLE_OUTPUT; +} + +/* TRIANGLE WAVE +** ============= +** reg0: 7=holdnote, 6-0=linear length counter +** reg2: low 8 bits of frequency +** reg3: 7-3=length counter, 2-0=high 3 bits of frequency +*/ +#define APU_TRIANGLE_OUTPUT (chan->output_vol + (chan->output_vol >> 2)) +static int32 +apu_triangle (triangle_t * chan) +{ + APU_VOLUME_DECAY (chan->output_vol); + + if (FALSE == chan->enabled || 0 == chan->vbl_length) + return APU_TRIANGLE_OUTPUT; + + if (chan->counter_started) { + if (chan->linear_length > 0) + chan->linear_length--; + if (chan->vbl_length && FALSE == chan->holdnote) + chan->vbl_length--; + } else if (FALSE == chan->holdnote && chan->write_latency) { + if (--chan->write_latency == 0) + chan->counter_started = TRUE; + } +/* + if (chan->countmode == COUNTMODE_COUNT) + { + if (chan->linear_length > 0) + chan->linear_length--; + if (chan->vbl_length) + chan->vbl_length--; + } +*/ + if (0 == chan->linear_length || chan->freq < APU_TO_FIXED (4)) /* inaudible */ + return APU_TRIANGLE_OUTPUT; + + chan->phaseacc -= apu->cycle_rate; /* # of cycles per sample */ + while (chan->phaseacc < 0) { + chan->phaseacc += chan->freq; + chan->adder = (chan->adder + 1) & 0x1F; + + if (chan->adder & 0x10) + chan->output_vol -= (2 << 8); + else + chan->output_vol += (2 << 8); + } + + return APU_TRIANGLE_OUTPUT; +} + + +/* WHITE NOISE CHANNEL +** =================== +** reg0: 0-3=volume, 4=envelope, 5=hold +** reg2: 7=small(93 byte) sample,3-0=freq lookup +** reg3: 7-4=vbl length counter +*/ +#define APU_NOISE_OUTPUT ((chan->output_vol + chan->output_vol + chan->output_vol) >> 2) + +static int32 +apu_noise (noise_t * chan) +{ + int32 outvol; + +#if defined(APU_OVERSAMPLE) && defined(REALTIME_NOISE) +#else + int32 noise_bit; +#endif +#ifdef APU_OVERSAMPLE + int num_times; + int32 total; +#endif + + APU_VOLUME_DECAY (chan->output_vol); + + if (FALSE == chan->enabled || 0 == chan->vbl_length) + return APU_NOISE_OUTPUT; + + /* vbl length counter */ + if (FALSE == chan->holdnote) + chan->vbl_length--; + + /* envelope decay at a rate of (env_delay + 1) / 240 secs */ + chan->env_phase -= 4; /* 240/60 */ + while (chan->env_phase < 0) { + chan->env_phase += chan->env_delay; + + if (chan->holdnote) + chan->env_vol = (chan->env_vol + 1) & 0x0F; + else if (chan->env_vol < 0x0F) + chan->env_vol++; + } + + chan->phaseacc -= apu->cycle_rate; /* # of cycles per sample */ + if (chan->phaseacc >= 0) + return APU_NOISE_OUTPUT; + +#ifdef APU_OVERSAMPLE + num_times = total = 0; + + if (chan->fixed_envelope) + outvol = chan->volume << 8; /* fixed volume */ + else + outvol = (chan->env_vol ^ 0x0F) << 8; +#endif + + while (chan->phaseacc < 0) { + chan->phaseacc += chan->freq; + +#ifdef REALTIME_NOISE + +#ifdef APU_OVERSAMPLE + if (shift_register15 (chan->xor_tap)) + total += outvol; + else + total -= outvol; + + num_times++; +#else + noise_bit = shift_register15 (chan->xor_tap); +#endif + +#else + chan->cur_pos++; + + if (chan->short_sample) { + if (APU_NOISE_93 == chan->cur_pos) + chan->cur_pos = 0; + } else { + if (APU_NOISE_32K == chan->cur_pos) + chan->cur_pos = 0; + } + +#ifdef APU_OVERSAMPLE + if (chan->short_sample) + noise_bit = noise_short_lut[chan->cur_pos]; + else + noise_bit = noise_long_lut[chan->cur_pos]; + + if (noise_bit) + total += outvol; + else + total -= outvol; + + num_times++; +#endif +#endif /* REALTIME_NOISE */ + } + +#ifdef APU_OVERSAMPLE + chan->output_vol = total / num_times; +#else + if (chan->fixed_envelope) + outvol = chan->volume << 8; /* fixed volume */ + else + outvol = (chan->env_vol ^ 0x0F) << 8; + +#ifndef REALTIME_NOISE + if (chan->short_sample) + noise_bit = noise_short_lut[chan->cur_pos]; + else + noise_bit = noise_long_lut[chan->cur_pos]; +#endif /* !REALTIME_NOISE */ + + if (noise_bit) + chan->output_vol = outvol; + else + chan->output_vol = -outvol; +#endif + + return APU_NOISE_OUTPUT; +} + + +INLINE void +apu_dmcreload (dmc_t * chan) +{ + chan->address = chan->cached_addr; + chan->dma_length = chan->cached_dmalength; + chan->irq_occurred = FALSE; +} + +/* DELTA MODULATION CHANNEL +** ========================= +** reg0: 7=irq gen, 6=looping, 3-0=pointer to clock table +** reg1: output dc level, 6 bits unsigned +** reg2: 8 bits of 64-byte aligned address offset : $C000 + (value * 64) +** reg3: length, (value * 16) + 1 +*/ +#define APU_DMC_OUTPUT ((chan->output_vol + chan->output_vol + chan->output_vol) >> 2) +static int32 +apu_dmc (dmc_t * chan) +{ + int delta_bit; + + APU_VOLUME_DECAY (chan->output_vol); + + /* only process when channel is alive */ + if (chan->dma_length) { + chan->phaseacc -= apu->cycle_rate; /* # of cycles per sample */ + + while (chan->phaseacc < 0) { + chan->phaseacc += chan->freq; + + delta_bit = (chan->dma_length & 7) ^ 7; + + if (7 == delta_bit) { + chan->cur_byte = nes6502_getbyte (chan->address); + + /* steal a cycle from CPU */ + nes6502_setdma (1); + + if (0xFFFF == chan->address) + chan->address = 0x8000; + else + chan->address++; + } + + if (--chan->dma_length == 0) { + /* if loop bit set, we're cool to retrigger sample */ + if (chan->looping) + apu_dmcreload (chan); + else { + /* check to see if we should generate an irq */ + if (chan->irq_gen) { + chan->irq_occurred = TRUE; + nes6502_irq (); + } + + /* bodge for timestamp queue */ + chan->enabled = FALSE; + break; + } + } + + /* positive delta */ + if (chan->cur_byte & (1 << delta_bit)) { + if (chan->regs[1] < 0x7D) { + chan->regs[1] += 2; + chan->output_vol += (2 << 8); + } +/* + if (chan->regs[1] < 0x3F) + chan->regs[1]++; + + chan->output_vol &= ~(0x7E << 8); + chan->output_vol |= ((chan->regs[1] << 1) << 8); +*/ + } + /* negative delta */ + else { + if (chan->regs[1] > 1) { + chan->regs[1] -= 2; + chan->output_vol -= (2 << 8); + } + +/* + if (chan->regs[1] > 0) + chan->regs[1]--; + + chan->output_vol &= ~(0x7E << 8); + chan->output_vol |= ((chan->regs[1] << 1) << 8); +*/ + } + } + } + + return APU_DMC_OUTPUT; +} + + +static void +apu_regwrite (uint32 address, uint8 value) +{ + int chan; + + ASSERT (apu); + switch (address) { + /* rectangles */ + case APU_WRA0: + case APU_WRB0: + chan = (address & 4) ? 1 : 0; + apu->rectangle[chan].regs[0] = value; + + apu->rectangle[chan].volume = value & 0x0F; + apu->rectangle[chan].env_delay = decay_lut[value & 0x0F]; + apu->rectangle[chan].holdnote = (value & 0x20) ? TRUE : FALSE; + apu->rectangle[chan].fixed_envelope = (value & 0x10) ? TRUE : FALSE; + apu->rectangle[chan].duty_flip = duty_lut[value >> 6]; + break; + + case APU_WRA1: + case APU_WRB1: + chan = (address & 4) ? 1 : 0; + apu->rectangle[chan].regs[1] = value; + apu->rectangle[chan].sweep_on = (value & 0x80) ? TRUE : FALSE; + apu->rectangle[chan].sweep_shifts = value & 7; + apu->rectangle[chan].sweep_delay = decay_lut[(value >> 4) & 7]; + + apu->rectangle[chan].sweep_inc = (value & 0x08) ? TRUE : FALSE; + apu->rectangle[chan].freq_limit = APU_TO_FIXED (freq_limit[value & 7]); + break; + + case APU_WRA2: + case APU_WRB2: + chan = (address & 4) ? 1 : 0; + apu->rectangle[chan].regs[2] = value; +// if (apu->rectangle[chan].enabled) + apu->rectangle[chan].freq = + APU_TO_FIXED ((((apu->rectangle[chan].regs[3] & 7) << 8) + value) + + 1); + break; + + case APU_WRA3: + case APU_WRB3: + chan = (address & 4) ? 1 : 0; + apu->rectangle[chan].regs[3] = value; + +// if (apu->rectangle[chan].enabled) + { + apu->rectangle[chan].vbl_length = vbl_lut[value >> 3]; + apu->rectangle[chan].env_vol = 0; + apu->rectangle[chan].freq = + APU_TO_FIXED ((((value & 7) << 8) + apu->rectangle[chan].regs[2]) + + 1); + apu->rectangle[chan].adder = 0; + } + break; + + /* triangle */ + case APU_WRC0: +/* + if (0 == (apu->triangle.regs[0] & 0x80)) + apu->triangle.countmode = COUNTMODE_COUNT; + else + { + if (apu->triangle.countmode == COUNTMODE_LOAD && apu->triangle.vbl_length) + apu->triangle.linear_length = trilength_lut[value & 0x7F]; + + if (0 == (value & 0x80)) + apu->triangle.countmode = COUNTMODE_COUNT; + } +*/ + apu->triangle.regs[0] = value; + + apu->triangle.holdnote = (value & 0x80) ? TRUE : FALSE; + + +// if (apu->triangle.enabled) + { + if (FALSE == apu->triangle.counter_started && apu->triangle.vbl_length) + apu->triangle.linear_length = trilength_lut[value & 0x7F]; + } + + break; + + case APU_WRC2: + + apu->triangle.regs[1] = value; + +// if (apu->triangle.enabled) + apu->triangle.freq = + APU_TO_FIXED ((((apu->triangle.regs[2] & 7) << 8) + value) + 1); + break; + + case APU_WRC3: + + apu->triangle.regs[2] = value; + + /* this is somewhat of a hack. there appears to be some latency on + ** the Real Thing between when trireg0 is written to and when the + ** linear length counter actually begins its countdown. we want to + ** prevent the case where the program writes to the freq regs first, + ** then to reg 0, and the counter accidentally starts running because + ** of the sound queue's timestamp processing. + ** + ** set latency to a couple scanlines -- should be plenty of time for + ** the 6502 code to do a couple of table dereferences and load up the + ** other triregs + */ + + /* 06/13/00 MPC -- seems to work OK */ + apu->triangle.write_latency = + (int) (2 * NES_SCANLINE_CYCLES / APU_FROM_FIXED (apu->cycle_rate)); +/* + apu->triangle.linear_length = trilength_lut[apu->triangle.regs[0] & 0x7F]; + if (0 == (apu->triangle.regs[0] & 0x80)) + apu->triangle.countmode = COUNTMODE_COUNT; + else + apu->triangle.countmode = COUNTMODE_LOAD; +*/ +// if (apu->triangle.enabled) + { + apu->triangle.freq = + APU_TO_FIXED ((((value & 7) << 8) + apu->triangle.regs[1]) + 1); + apu->triangle.vbl_length = vbl_lut[value >> 3]; + apu->triangle.counter_started = FALSE; + apu->triangle.linear_length = + trilength_lut[apu->triangle.regs[0] & 0x7F]; + } + + break; + + /* noise */ + case APU_WRD0: + apu->noise.regs[0] = value; + apu->noise.env_delay = decay_lut[value & 0x0F]; + apu->noise.holdnote = (value & 0x20) ? TRUE : FALSE; + apu->noise.fixed_envelope = (value & 0x10) ? TRUE : FALSE; + apu->noise.volume = value & 0x0F; + break; + + case APU_WRD2: + apu->noise.regs[1] = value; + apu->noise.freq = APU_TO_FIXED (noise_freq[value & 0x0F]); + +#ifdef REALTIME_NOISE + apu->noise.xor_tap = (value & 0x80) ? 0x40 : 0x02; +#else + /* detect transition from long->short sample */ + if ((value & 0x80) && FALSE == apu->noise.short_sample) { + /* recalculate short noise buffer */ + shift_register15 (noise_short_lut, APU_NOISE_93); + apu->noise.cur_pos = 0; + } + apu->noise.short_sample = (value & 0x80) ? TRUE : FALSE; +#endif + break; + + case APU_WRD3: + apu->noise.regs[2] = value; + +// if (apu->noise.enabled) + { + apu->noise.vbl_length = vbl_lut[value >> 3]; + apu->noise.env_vol = 0; /* reset envelope */ + } + break; + + /* DMC */ + case APU_WRE0: + apu->dmc.regs[0] = value; + + apu->dmc.freq = APU_TO_FIXED (dmc_clocks[value & 0x0F]); + apu->dmc.looping = (value & 0x40) ? TRUE : FALSE; + + if (value & 0x80) + apu->dmc.irq_gen = TRUE; + else { + apu->dmc.irq_gen = FALSE; + apu->dmc.irq_occurred = FALSE; + } + break; + + case APU_WRE1: /* 7-bit DAC */ + /* add the _delta_ between written value and + ** current output level of the volume reg + */ + value &= 0x7F; /* bit 7 ignored */ + apu->dmc.output_vol += ((value - apu->dmc.regs[1]) << 8); + apu->dmc.regs[1] = value; +/* + apu->dmc.output_vol = (value & 0x7F) << 8; + apu->dmc.regs[1] = (value & 0x7E) >> 1; +*/ + break; + + case APU_WRE2: + apu->dmc.regs[2] = value; + apu->dmc.cached_addr = 0xC000 + (uint16) (value << 6); + break; + + case APU_WRE3: + apu->dmc.regs[3] = value; + apu->dmc.cached_dmalength = ((value << 4) + 1) << 3; + break; + + case APU_SMASK: + /* bodge for timestamp queue */ + apu->dmc.enabled = (value & 0x10) ? TRUE : FALSE; + + apu->enable_reg = value; + + for (chan = 0; chan < 2; chan++) { + if (value & (1 << chan)) + apu->rectangle[chan].enabled = TRUE; + else { + apu->rectangle[chan].enabled = FALSE; + apu->rectangle[chan].vbl_length = 0; + } + } + + if (value & 0x04) + apu->triangle.enabled = TRUE; + else { + apu->triangle.enabled = FALSE; + apu->triangle.vbl_length = 0; + apu->triangle.linear_length = 0; + apu->triangle.counter_started = FALSE; + apu->triangle.write_latency = 0; + } + + if (value & 0x08) + apu->noise.enabled = TRUE; + else { + apu->noise.enabled = FALSE; + apu->noise.vbl_length = 0; + } + + if (value & 0x10) { + if (0 == apu->dmc.dma_length) + apu_dmcreload (&apu->dmc); + } else + apu->dmc.dma_length = 0; + + apu->dmc.irq_occurred = FALSE; + break; + + /* unused, but they get hit in some mem-clear loops */ + case 0x4009: + case 0x400D: + break; + + default: + break; + } +} + +/* Read from $4000-$4017 */ +uint8 +apu_read (uint32 address) +{ + uint8 value; + + ASSERT (apu); + + switch (address) { + case APU_SMASK: + /* seems that bit 6 denotes vblank -- return 1 for now */ + value = 0x40; + + /* Return 1 in 0-5 bit pos if a channel is playing */ + if (apu->rectangle[0].enabled && apu->rectangle[0].vbl_length) + value |= 0x01; + if (apu->rectangle[1].enabled && apu->rectangle[1].vbl_length) + value |= 0x02; + if (apu->triangle.enabled && apu->triangle.vbl_length) + value |= 0x04; + if (apu->noise.enabled && apu->noise.vbl_length) + value |= 0x08; + + //if (apu->dmc.dma_length) + /* bodge for timestamp queue */ + if (apu->dmc.enabled) + value |= 0x10; + + if (apu->dmc.irq_occurred) + value |= 0x80; + + break; + +#ifndef NSF_PLAYER + case APU_JOY0: + value = input_get (INP_JOYPAD0); + break; + + case APU_JOY1: + value = + input_get (INP_ZAPPER | INP_JOYPAD1 + /*| INP_ARKANOID *//*| INP_POWERPAD */ ); + break; +#endif /* !NSF_PLAYER */ + + default: + value = (address >> 8); /* heavy capacitance on data bus */ + break; + } + + return value; +} + + +void +apu_write (uint32 address, uint8 value) +{ +#ifndef NSF_PLAYER + static uint8 last_write; +#endif /* !NSF_PLAYER */ + apudata_t d; + + switch (address) { + case 0x4015: + /* bodge for timestamp queue */ + apu->dmc.enabled = (value & 0x10) ? TRUE : FALSE; + + case 0x4000: + case 0x4001: + case 0x4002: + case 0x4003: + case 0x4004: + case 0x4005: + case 0x4006: + case 0x4007: + case 0x4008: + case 0x4009: + case 0x400A: + case 0x400B: + case 0x400C: + case 0x400D: + case 0x400E: + case 0x400F: + case 0x4010: + case 0x4011: + case 0x4012: + case 0x4013: + d.timestamp = nes6502_getcycles (FALSE); + d.address = address; + d.value = value; + apu_enqueue (&d); + break; + +#ifndef NSF_PLAYER + case APU_OAMDMA: + ppu_oamdma (address, value); + break; + + case APU_JOY0: + /* VS system VROM switching */ + mmc_vsvrom (value & 4); + + /* see if we need to strobe them joypads */ + value &= 1; + if ((0 == value) && last_write) + input_strobe (); + last_write = value; + break; + + case APU_JOY1: /* Some kind of IRQ control business */ + break; + +#endif /* !NSF_PLAYER */ + + default: + break; + } +} + +void +apu_getpcmdata (void **data, int *num_samples, int *sample_bits) +{ + ASSERT (apu); + *data = apu->buffer; + *num_samples = apu->num_samples; + *sample_bits = apu->sample_bits; +} + + +void +apu_process (void *buffer, int num_samples) +{ + apudata_t *d; + uint32 elapsed_cycles; + static int32 prev_sample = 0; + int32 next_sample, accum; + + ASSERT (apu); + + /* grab it, keep it local for speed */ + elapsed_cycles = (uint32) apu->elapsed_cycles; + + /* BLEH */ + apu->buffer = buffer; + + while (num_samples--) { + while ((FALSE == APU_QEMPTY ()) + && (apu->queue[apu->q_tail].timestamp <= elapsed_cycles)) { + d = apu_dequeue (); + apu_regwrite (d->address, d->value); + } + + elapsed_cycles += APU_FROM_FIXED (apu->cycle_rate); + + accum = 0; + if (apu->mix_enable[0]) + accum += apu_rectangle (&apu->rectangle[0]); + if (apu->mix_enable[1]) + accum += apu_rectangle (&apu->rectangle[1]); + if (apu->mix_enable[2]) + accum += apu_triangle (&apu->triangle); + if (apu->mix_enable[3]) + accum += apu_noise (&apu->noise); + if (apu->mix_enable[4]) + accum += apu_dmc (&apu->dmc); + + if (apu->ext && apu->mix_enable[5]) + accum += apu->ext->process (); + + /* do any filtering */ + if (APU_FILTER_NONE != apu->filter_type) { + next_sample = accum; + + if (APU_FILTER_LOWPASS == apu->filter_type) { + accum += prev_sample; + accum >>= 1; + } else + accum = (accum + accum + accum + prev_sample) >> 2; + + prev_sample = next_sample; + } + + /* little extra kick for the kids */ + accum <<= 1; + + /* prevent clipping */ + if (accum > 0x7FFF) + accum = 0x7FFF; + else if (accum < -0x8000) + accum = -0x8000; + + /* signed 16-bit output, unsigned 8-bit */ + if (16 == apu->sample_bits) { + int16 *t = buffer; + + *t++ = (int16) accum; + buffer = t; + } else { + uint8 *t = buffer; + + *t++ = (accum >> 8) ^ 0x80; + buffer = t; + } + } + + /* resync cycle counter */ + apu->elapsed_cycles = nes6502_getcycles (FALSE); +} + +/* set the filter type */ +void +apu_setfilter (int filter_type) +{ + ASSERT (apu); + apu->filter_type = filter_type; +} + +void +apu_reset (void) +{ + uint32 address; + + ASSERT (apu); + + apu->elapsed_cycles = 0; + memset (&apu->queue, 0, APUQUEUE_SIZE * sizeof (apudata_t)); + apu->q_head = 0; + apu->q_tail = 0; + + /* use to avoid bugs =) */ + for (address = 0x4000; address <= 0x4013; address++) + apu_regwrite (address, 0); + +#ifdef NSF_PLAYER + apu_regwrite (0x400C, 0x10); /* silence noise channel on NSF start */ + apu_regwrite (0x4015, 0x0F); +#else + apu_regwrite (0x4015, 0); +#endif /* NSF_PLAYER */ + + if (apu->ext) + apu->ext->reset (); +} + +void +apu_build_luts (int num_samples) +{ + int i; + + /* lut used for enveloping and frequency sweeps */ + for (i = 0; i < 16; i++) + decay_lut[i] = num_samples * (i + 1); + + /* used for note length, based on vblanks and size of audio buffer */ + for (i = 0; i < 32; i++) + vbl_lut[i] = vbl_length[i] * num_samples; + + /* triangle wave channel's linear length table */ + for (i = 0; i < 128; i++) + trilength_lut[i] = (i * num_samples) / 4; + +#ifndef REALTIME_NOISE + /* generate noise samples */ + shift_register15 (noise_long_lut, APU_NOISE_32K); + shift_register15 (noise_short_lut, APU_NOISE_93); +#endif /* !REALTIME_NOISE */ +} + +static void +apu_setactive (apu_t * active) +{ + ASSERT (active); + apu = active; +} + +/* Initializes emulated sound hardware, creates waveforms/voices */ +apu_t * +apu_create (int sample_rate, int refresh_rate, int sample_bits, boolean stereo) +{ + apu_t *temp_apu; + int channel; + + temp_apu = malloc (sizeof (apu_t)); + if (NULL == temp_apu) + return NULL; + + temp_apu->sample_rate = sample_rate; + temp_apu->refresh_rate = refresh_rate; + temp_apu->sample_bits = sample_bits; + + temp_apu->num_samples = sample_rate / refresh_rate; + /* turn into fixed point! */ + temp_apu->cycle_rate = (int32) (APU_BASEFREQ * 65536.0 / (float) sample_rate); + + /* build various lookup tables for apu */ + apu_build_luts (temp_apu->num_samples); + + /* set the update routine */ + temp_apu->process = apu_process; + temp_apu->ext = NULL; + + apu_setactive (temp_apu); + apu_reset (); + + for (channel = 0; channel < 6; channel++) + apu_setchan (channel, TRUE); + + apu_setfilter (APU_FILTER_LOWPASS); + + return temp_apu; +} + +apu_t * +apu_getcontext (void) +{ + return apu; +} + +void +apu_destroy (apu_t * src_apu) +{ + if (src_apu) { + void *t = src_apu; + + if (src_apu->ext) + src_apu->ext->shutdown (); + free (t); + } +} + +void +apu_setext (apu_t * src_apu, apuext_t * ext) +{ + ASSERT (src_apu); + + src_apu->ext = ext; + + /* initialize it */ + if (src_apu->ext) + src_apu->ext->init (); +} + +/* this exists for external mixing routines */ +int32 +apu_getcyclerate (void) +{ + ASSERT (apu); + return apu->cycle_rate; +} + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.19 2000/07/04 04:53:26 matt +** minor changes, sound amplification +** +** Revision 1.18 2000/07/03 02:18:53 matt +** much better external module exporting +** +** Revision 1.17 2000/06/26 11:01:55 matt +** made triangle a tad quieter +** +** Revision 1.16 2000/06/26 05:10:33 matt +** fixed cycle rate generation accuracy +** +** Revision 1.15 2000/06/26 05:00:37 matt +** cleanups +** +** Revision 1.14 2000/06/23 11:06:24 matt +** more faithful mixing of channels +** +** Revision 1.13 2000/06/23 03:29:27 matt +** cleaned up external sound inteface +** +** Revision 1.12 2000/06/20 00:08:39 matt +** bugfix to rectangle wave +** +** Revision 1.11 2000/06/13 13:48:58 matt +** fixed triangle write latency for fixed point apu cycle rate +** +** Revision 1.10 2000/06/12 01:14:36 matt +** minor change to clipping extents +** +** Revision 1.9 2000/06/09 20:00:56 matt +** fixed noise hiccup in NSF player mode +** +** Revision 1.8 2000/06/09 16:49:02 matt +** removed all floating point from sound generation +** +** Revision 1.7 2000/06/09 15:12:28 matt +** initial revision +** +*/ diff --git a/gst/nsf/nes_apu.h b/gst/nsf/nes_apu.h new file mode 100644 index 00000000..fd94798d --- /dev/null +++ b/gst/nsf/nes_apu.h @@ -0,0 +1,354 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** nes_apu.h +** +** NES APU emulation header file +** $Id$ +*/ + +#ifndef _NES_APU_H_ +#define _NES_APU_H_ + +#ifdef __GNUC__ +#define INLINE static inline +#elif defined(WIN32) +#define INLINE static __inline +#else +#define INLINE static +#endif + +/* define this for realtime generated noise */ +#define REALTIME_NOISE + +#define APU_WRA0 0x4000 +#define APU_WRA1 0x4001 +#define APU_WRA2 0x4002 +#define APU_WRA3 0x4003 +#define APU_WRB0 0x4004 +#define APU_WRB1 0x4005 +#define APU_WRB2 0x4006 +#define APU_WRB3 0x4007 +#define APU_WRC0 0x4008 +#define APU_WRC2 0x400A +#define APU_WRC3 0x400B +#define APU_WRD0 0x400C +#define APU_WRD2 0x400E +#define APU_WRD3 0x400F +#define APU_WRE0 0x4010 +#define APU_WRE1 0x4011 +#define APU_WRE2 0x4012 +#define APU_WRE3 0x4013 + +#define APU_OAMDMA 0x4014 +#define APU_SMASK 0x4015 +#define APU_JOY0 0x4016 +#define APU_JOY1 0x4017 + +/* length of generated noise */ +#define APU_NOISE_32K 0x7FFF +#define APU_NOISE_93 93 + +#define APU_BASEFREQ (NES_MASTER_CLOCK / 12) + +/* to/from 16.16 fixed point */ +#define APU_TO_FIXED(x) ((x) << 16) +#define APU_FROM_FIXED(x) ((x) >> 16) + + +/* channel structures */ +/* As much data as possible is precalculated, +** to keep the sample processing as lean as possible +*/ + +typedef struct rectangle_s +{ + uint8 regs[4]; + + boolean enabled; + + int32 phaseacc; + int32 freq; + int32 output_vol; + boolean fixed_envelope; + boolean holdnote; + uint8 volume; + + int32 sweep_phase; + int32 sweep_delay; + boolean sweep_on; + uint8 sweep_shifts; + uint8 sweep_length; + boolean sweep_inc; + int32 freq_limit; + + int32 env_phase; + int32 env_delay; + uint8 env_vol; + + int vbl_length; + uint8 adder; + int duty_flip; +} rectangle_t; + +/* +enum +{ + COUNTMODE_LOAD, + COUNTMODE_COUNT +}; +*/ + +typedef struct triangle_s +{ + uint8 regs[3]; + + boolean enabled; + + int32 freq; + int32 phaseacc; + int32 output_vol; + + uint8 adder; + + boolean holdnote; + boolean counter_started; + /* quasi-hack */ + int write_latency; + + /* boolean countmode; */ + + int vbl_length; + int linear_length; + +} triangle_t; + + +typedef struct noise_s +{ + uint8 regs[3]; + + boolean enabled; + + int32 freq; + int32 phaseacc; + int32 output_vol; + + int32 env_phase; + int32 env_delay; + uint8 env_vol; + boolean fixed_envelope; + boolean holdnote; + + uint8 volume; + + int vbl_length; + +#ifdef REALTIME_NOISE + uint8 xor_tap; +#else + boolean short_sample; + int cur_pos; +#endif /* REALTIME_NOISE */ +} noise_t; + +typedef struct dmc_s +{ + uint8 regs[4]; + + /* bodge for timestamp queue */ + boolean enabled; + + int32 freq; + int32 phaseacc; + int32 output_vol; + + uint32 address; + uint32 cached_addr; + int dma_length; + int cached_dmalength; + uint8 cur_byte; + + boolean looping; + boolean irq_gen; + boolean irq_occurred; + +} dmc_t; + +enum +{ + APU_FILTER_NONE, + APU_FILTER_LOWPASS, + APU_FILTER_WEIGHTED +}; + +typedef struct +{ + uint32 min_range, max_range; + uint8 (*read_func)(uint32 address); +} apu_memread; + +typedef struct +{ + uint32 min_range, max_range; + void (*write_func)(uint32 address, uint8 value); +} apu_memwrite; + +/* external sound chip stuff */ +typedef struct apuext_s +{ + void (*init)(void); + void (*shutdown)(void); + void (*reset)(void); + int32 (*process)(void); + apu_memread *mem_read; + apu_memwrite *mem_write; +} apuext_t; + + +/* APU queue structure */ +#define APUQUEUE_SIZE 4096 +#define APUQUEUE_MASK (APUQUEUE_SIZE - 1) + +/* apu ring buffer member */ +typedef struct apudata_s +{ + uint32 timestamp, address; + uint8 value; +} apudata_t; + + +typedef struct apu_s +{ + rectangle_t rectangle[2]; + triangle_t triangle; + noise_t noise; + dmc_t dmc; + uint8 enable_reg; + + apudata_t queue[APUQUEUE_SIZE]; + int q_head, q_tail; + uint32 elapsed_cycles; + + void *buffer; /* pointer to output buffer */ + int num_samples; + + boolean mix_enable[6]; + int filter_type; + + int32 cycle_rate; + + int sample_rate; + int sample_bits; + int refresh_rate; + + void (*process)(void *buffer, int num_samples); + + /* external sound chip */ + apuext_t *ext; +} apu_t; + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Function prototypes */ +extern apu_t *apu_create(int sample_rate, int refresh_rate, int sample_bits, boolean stereo); +extern void apu_destroy(apu_t *apu); +extern void apu_setext(apu_t *apu, apuext_t *ext); +extern void apu_setfilter(int filter_type); +extern void apu_process(void *buffer, int num_samples); +extern void apu_reset(void); +extern void apu_setchan(int chan, boolean enabled); +extern int32 apu_getcyclerate(void); +extern apu_t *apu_getcontext(void); + +extern uint8 apu_read(uint32 address); +extern void apu_write(uint32 address, uint8 value); + +/* for visualization */ +extern void apu_getpcmdata(void **data, int *num_samples, int *sample_bits); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _NES_APU_H_ */ + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.12 2000/07/04 04:54:48 matt +** minor changes that helped with MAME +** +** Revision 1.11 2000/07/03 02:18:53 matt +** much better external module exporting +** +** Revision 1.10 2000/06/26 05:00:37 matt +** cleanups +** +** Revision 1.9 2000/06/23 03:29:28 matt +** cleaned up external sound inteface +** +** Revision 1.8 2000/06/20 04:06:16 matt +** migrated external sound definition to apu module +** +** Revision 1.7 2000/06/20 00:07:35 matt +** added convenience members to apu_t struct +** +** Revision 1.6 2000/06/09 16:49:02 matt +** removed all floating point from sound generation +** +** Revision 1.5 2000/06/09 15:12:28 matt +** initial revision +** +*/ diff --git a/gst/nsf/nsf.c b/gst/nsf/nsf.c new file mode 100644 index 00000000..8b89f23d --- /dev/null +++ b/gst/nsf/nsf.c @@ -0,0 +1,648 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** nsf.c +** +** NSF loading/saving related functions +** $Id$ +*/ + +#include <stdio.h> +#include <string.h> +#include "types.h" +#include "nsf.h" +#include "log.h" +#include "nes6502.h" +#include "nes_apu.h" +#include "vrcvisnd.h" +#include "vrc7_snd.h" +#include "mmc5_snd.h" +#include "fds_snd.h" + +/* TODO: bleh! should encapsulate in NSF */ +#define MAX_ADDRESS_HANDLERS 32 +static nes6502_memread nsf_readhandler[MAX_ADDRESS_HANDLERS]; +static nes6502_memwrite nsf_writehandler[MAX_ADDRESS_HANDLERS]; + +static nsf_t *cur_nsf = NULL; + +static void +nsf_setcontext (nsf_t * nsf) +{ + ASSERT (nsf); + cur_nsf = nsf; +} + +static uint8 +read_mirrored_ram (uint32 address) +{ + return cur_nsf->cpu->mem_page[0][address & 0x7FF]; +} + +static void +write_mirrored_ram (uint32 address, uint8 value) +{ + cur_nsf->cpu->mem_page[0][address & 0x7FF] = value; +} + +/* can be used for both banked and non-bankswitched NSFs */ +static void +nsf_bankswitch (uint32 address, uint8 value) +{ + int cpu_page; + uint8 *offset; + + cpu_page = address & 0x0F; + offset = (cur_nsf->data - (cur_nsf->load_addr & 0x0FFF)) + (value << 12); + + nes6502_getcontext (cur_nsf->cpu); + cur_nsf->cpu->mem_page[cpu_page] = offset; + nes6502_setcontext (cur_nsf->cpu); +} + +static nes6502_memread default_readhandler[] = { + {0x0800, 0x1FFF, read_mirrored_ram}, + {0x4000, 0x4017, apu_read}, + {-1, -1, NULL} +}; + +static nes6502_memwrite default_writehandler[] = { + {0x0800, 0x1FFF, write_mirrored_ram}, + {0x4000, 0x4017, apu_write}, + {0x5FF6, 0x5FFF, nsf_bankswitch}, + {-1, -1, NULL} +}; + +static uint8 +invalid_read (uint32 address) +{ +#ifdef NOFRENDO_DEBUG + log_printf ("filthy NSF read from $%04X\n", address); +#endif /* NOFRENDO_DEBUG */ + + return 0xFF; +} + +static void +invalid_write (uint32 address, uint8 value) +{ +#ifdef NOFRENDO_DEBUG + log_printf ("filthy NSF tried to write $%02X to $%04X\n", value, address); +#endif /* NOFRENDO_DEBUG */ +} + +/* set up the address handlers that the CPU uses */ +static void +build_address_handlers (nsf_t * nsf) +{ + int count, num_handlers; + + memset (nsf_readhandler, 0, sizeof (nsf_readhandler)); + memset (nsf_writehandler, 0, sizeof (nsf_writehandler)); + + num_handlers = 0; + for (count = 0; num_handlers < MAX_ADDRESS_HANDLERS; count++, num_handlers++) { + if (NULL == default_readhandler[count].read_func) + break; + + memcpy (&nsf_readhandler[num_handlers], &default_readhandler[count], + sizeof (nes6502_memread)); + } + + if (nsf->apu->ext) { + if (NULL != nsf->apu->ext->mem_read) { + for (count = 0; num_handlers < MAX_ADDRESS_HANDLERS; + count++, num_handlers++) { + if (NULL == nsf->apu->ext->mem_read[count].read_func) + break; + + memcpy (&nsf_readhandler[num_handlers], &nsf->apu->ext->mem_read[count], + sizeof (nes6502_memread)); + } + } + } + + /* catch-all for bad reads */ + nsf_readhandler[num_handlers].min_range = 0x2000; /* min address */ + nsf_readhandler[num_handlers].max_range = 0x5BFF; /* max address */ + nsf_readhandler[num_handlers].read_func = invalid_read; /* handler */ + num_handlers++; + nsf_readhandler[num_handlers].min_range = -1; + nsf_readhandler[num_handlers].max_range = -1; + nsf_readhandler[num_handlers].read_func = NULL; + num_handlers++; + ASSERT (num_handlers <= MAX_ADDRESS_HANDLERS); + + num_handlers = 0; + for (count = 0; num_handlers < MAX_ADDRESS_HANDLERS; count++, num_handlers++) { + if (NULL == default_writehandler[count].write_func) + break; + + memcpy (&nsf_writehandler[num_handlers], &default_writehandler[count], + sizeof (nes6502_memwrite)); + } + + if (nsf->apu->ext) { + if (NULL != nsf->apu->ext->mem_write) { + for (count = 0; num_handlers < MAX_ADDRESS_HANDLERS; + count++, num_handlers++) { + if (NULL == nsf->apu->ext->mem_write[count].write_func) + break; + + memcpy (&nsf_writehandler[num_handlers], + &nsf->apu->ext->mem_write[count], sizeof (nes6502_memwrite)); + } + } + } + + /* catch-all for bad writes */ + nsf_writehandler[num_handlers].min_range = 0x2000; /* min address */ + nsf_writehandler[num_handlers].max_range = 0x5BFF; /* max address */ + nsf_writehandler[num_handlers].write_func = invalid_write; /* handler */ + num_handlers++; + /* protect region at $8000-$FFFF */ + nsf_writehandler[num_handlers].min_range = 0x8000; /* min address */ + nsf_writehandler[num_handlers].max_range = 0xFFFF; /* max address */ + nsf_writehandler[num_handlers].write_func = invalid_write; /* handler */ + num_handlers++; + nsf_writehandler[num_handlers].min_range = -1; + nsf_writehandler[num_handlers].max_range = -1; + nsf_writehandler[num_handlers].write_func = NULL; + num_handlers++; + ASSERT (num_handlers <= MAX_ADDRESS_HANDLERS); +} + +#define NSF_ROUTINE_LOC 0x5000 + +/* sets up a simple loop that calls the desired routine and spins */ +static void +nsf_setup_routine (uint32 address, uint8 a_reg, uint8 x_reg) +{ + uint8 *mem; + + nes6502_getcontext (cur_nsf->cpu); + mem = + cur_nsf->cpu->mem_page[NSF_ROUTINE_LOC >> 12] + + (NSF_ROUTINE_LOC & 0x0FFF); + + /* our lovely 4-byte 6502 NSF player */ + mem[0] = 0x20; /* JSR address */ + mem[1] = address & 0xFF; + mem[2] = address >> 8; + mem[3] = 0xF2; /* JAM (cpu kill op) */ + + cur_nsf->cpu->pc_reg = NSF_ROUTINE_LOC; + cur_nsf->cpu->a_reg = a_reg; + cur_nsf->cpu->x_reg = x_reg; + cur_nsf->cpu->y_reg = 0; + cur_nsf->cpu->s_reg = 0xFF; + + nes6502_setcontext (cur_nsf->cpu); +} + +/* retrieve any external soundchip driver */ +static apuext_t * +nsf_getext (nsf_t * nsf) +{ + switch (nsf->ext_sound_type) { + case EXT_SOUND_VRCVI: + return &vrcvi_ext; + + case EXT_SOUND_VRCVII: + return &vrc7_ext; + + case EXT_SOUND_FDS: + return &fds_ext; + + case EXT_SOUND_MMC5: + return &mmc5_ext; + + case EXT_SOUND_NAMCO106: + case EXT_SOUND_SUNSOFT_FME07: + case EXT_SOUND_NONE: + default: + return NULL; + } +} + +static void +nsf_inittune (nsf_t * nsf) +{ + uint8 bank, x_reg; + uint8 start_bank, num_banks; + + memset (nsf->cpu->mem_page[0], 0, 0x800); + memset (nsf->cpu->mem_page[6], 0, 0x1000); + memset (nsf->cpu->mem_page[7], 0, 0x1000); + + if (nsf->bankswitched) { + /* the first hack of the NSF spec! */ + if (EXT_SOUND_FDS == nsf->ext_sound_type) { + nsf_bankswitch (0x5FF6, nsf->bankswitch_info[6]); + nsf_bankswitch (0x5FF7, nsf->bankswitch_info[7]); + } + + for (bank = 0; bank < 8; bank++) + nsf_bankswitch (0x5FF8 + bank, nsf->bankswitch_info[bank]); + } else { + /* not bankswitched, just page in our standard stuff */ + ASSERT (nsf->load_addr + nsf->length <= 0x10000); + + /* avoid ripper filth */ + for (bank = 0; bank < 8; bank++) + nsf_bankswitch (0x5FF8 + bank, bank); + + start_bank = nsf->load_addr >> 12; + num_banks = ((nsf->load_addr + nsf->length - 1) >> 12) - start_bank + 1; + + for (bank = 0; bank < num_banks; bank++) + nsf_bankswitch (0x5FF0 + start_bank + bank, bank); + } + + /* determine PAL/NTSC compatibility shite */ + if (nsf->pal_ntsc_bits & NSF_DEDICATED_PAL) + x_reg = 1; + else + x_reg = 0; + + /* execute 1 frame or so; let init routine run free */ + nsf_setup_routine (nsf->init_addr, (uint8) (nsf->current_song - 1), x_reg); + nes6502_execute ((int) NES_FRAME_CYCLES); +} + +void +nsf_frame (nsf_t * nsf) +{ + /* future expansion =) */ + /*nsf_setcontext(nsf); */ + + /* one frame of NES processing */ + nsf_setup_routine (nsf->play_addr, 0, 0); + nes6502_execute ((int) NES_FRAME_CYCLES); +} + +/* Deallocate memory */ +void +nes_shutdown (nsf_t * nsf) +{ + int i; + void *mem; + + ASSERT (nsf); + + if (nsf->cpu) { + if (nsf->cpu->mem_page[0]) + free (nsf->cpu->mem_page[0]); + for (i = 5; i <= 7; i++) { + if (nsf->cpu->mem_page[i]) + free (nsf->cpu->mem_page[i]); + } + mem = nsf->cpu; + free (mem); + } +} + +void +nsf_init (void) +{ + nes6502_init (); +} + +/* Initialize NES CPU, hardware, etc. */ +static int +nsf_cpuinit (nsf_t * nsf) +{ + int i; + + nsf->cpu = malloc (sizeof (nes6502_context)); + if (NULL == nsf->cpu) + return -1; + + memset (nsf->cpu, 0, sizeof (nes6502_context)); + + nsf->cpu->mem_page[0] = malloc (0x800); + if (NULL == nsf->cpu->mem_page[0]) + return -1; + + /* allocate some space for the NSF "player" MMC5 EXRAM, and WRAM */ + for (i = 5; i <= 7; i++) { + nsf->cpu->mem_page[i] = malloc (0x1000); + if (NULL == nsf->cpu->mem_page[i]) + return -1; + } + + nsf->cpu->read_handler = nsf_readhandler; + nsf->cpu->write_handler = nsf_writehandler; + + return 0; +} + +static void +nsf_setup (nsf_t * nsf) +{ + int i; + + nsf->current_song = nsf->start_song; + + if (nsf->pal_ntsc_bits & NSF_DEDICATED_PAL) { + if (nsf->pal_speed) + nsf->playback_rate = 1000000 / nsf->pal_speed; + else + nsf->playback_rate = 50; /* 50 Hz */ + } else { + if (nsf->ntsc_speed) + nsf->playback_rate = 1000000 / nsf->ntsc_speed; + else + nsf->playback_rate = 60; /* 60 Hz */ + } + + nsf->bankswitched = FALSE; + + for (i = 0; i < 8; i++) { + if (nsf->bankswitch_info[i]) { + nsf->bankswitched = TRUE; + break; + } + } +} + +#ifdef HOST_LITTLE_ENDIAN +#define SWAP_16(x) (x) +#else /* !HOST_LITTLE_ENDIAN */ +#define SWAP_16(x) (((uint16) x >> 8) | (((uint16) x & 0xFF) << 8)) +#endif /* !HOST_LITTLE_ENDIAN */ + +/* Load a ROM image into memory */ +nsf_t * +nsf_load (char *filename, void *source, int length) +{ + FILE *fp = NULL; + char *new_fn = NULL; + nsf_t *temp_nsf; + + if (NULL == filename && NULL == source) + return NULL; + + if (NULL == source) { + fp = fopen (filename, "rb"); + + /* Didn't find the file? Maybe the .NSF extension was omitted */ + if (NULL == fp) { + new_fn = malloc (strlen (filename) + 5); + if (NULL == new_fn) + return NULL; + strcpy (new_fn, filename); + + if (NULL == strrchr (new_fn, '.')) + strcat (new_fn, ".nsf"); + + fp = fopen (new_fn, "rb"); + + if (NULL == fp) { + void *t = new_fn; + + log_printf ("could not find file '%s'\n", new_fn); + free (t); + return NULL; + } + } + } + + temp_nsf = malloc (sizeof (nsf_t)); + if (NULL == temp_nsf) + return NULL; + + /* Read in the header */ + if (NULL == source) + fread (temp_nsf, 1, NSF_HEADER_SIZE, fp); + else + memcpy (temp_nsf, source, NSF_HEADER_SIZE); + + if (memcmp (temp_nsf->id, NSF_MAGIC, 5)) { + if (NULL == source) { + void *t = new_fn; + + log_printf ("%s is not an NSF format file\n", new_fn); + fclose (fp); + free (t); + } + nsf_free (&temp_nsf); + return NULL; + } + + /* fixup endianness */ + temp_nsf->load_addr = SWAP_16 (temp_nsf->load_addr); + temp_nsf->init_addr = SWAP_16 (temp_nsf->init_addr); + temp_nsf->play_addr = SWAP_16 (temp_nsf->play_addr); + temp_nsf->ntsc_speed = SWAP_16 (temp_nsf->ntsc_speed); + temp_nsf->pal_speed = SWAP_16 (temp_nsf->pal_speed); + + /* we're now at position 80h */ + if (NULL == source) { + fseek (fp, 0, SEEK_END); + temp_nsf->length = ftell (fp) - NSF_HEADER_SIZE; + } else { + temp_nsf->length = length - NSF_HEADER_SIZE; + } + + /* Allocate NSF space, and load it up! */ + temp_nsf->data = malloc (temp_nsf->length); + if (NULL == temp_nsf->data) { + log_printf ("error allocating memory for NSF data\n"); + nsf_free (&temp_nsf); + return NULL; + } + + /* seek to end of header, read in data */ + if (NULL == source) { + fseek (fp, NSF_HEADER_SIZE, SEEK_SET); + fread (temp_nsf->data, temp_nsf->length, 1, fp); + + fclose (fp); + + + if (new_fn) { + void *t = new_fn; + + free (t); + } + } else + memcpy (temp_nsf->data, (uint8 *) source + NSF_HEADER_SIZE, + temp_nsf->length); + + /* Set up some variables */ + nsf_setup (temp_nsf); + + temp_nsf->apu = NULL; /* just make sure */ + + if (nsf_cpuinit (temp_nsf)) { + nsf_free (&temp_nsf); + return NULL; + } + + return temp_nsf; +} + +/* Free an NSF */ +void +nsf_free (nsf_t ** nsf) +{ + if (*nsf) { + if ((*nsf)->apu) + apu_destroy ((*nsf)->apu); + + nes_shutdown (*nsf); + + if ((*nsf)->data) { + void *mem = (*nsf)->data; + + free (mem); + } + + free (*nsf); + } +} + +void +nsf_setchan (nsf_t * nsf, int chan, boolean enabled) +{ + if (nsf) { + nsf_setcontext (nsf); + apu_setchan (chan, enabled); + } +} + +void +nsf_playtrack (nsf_t * nsf, int track, int sample_rate, int sample_bits, + boolean stereo) +{ + ASSERT (nsf); + + /* make this NSF the current context */ + nsf_setcontext (nsf); + + /* create the APU */ + if (nsf->apu) + apu_destroy (nsf->apu); + + nsf->apu = apu_create (sample_rate, nsf->playback_rate, sample_bits, stereo); + if (NULL == nsf->apu) { + nsf_free (&nsf); + return; + } + + apu_setext (nsf->apu, nsf_getext (nsf)); + + /* go ahead and init all the read/write handlers */ + build_address_handlers (nsf); + + /* convenience? */ + nsf->process = nsf->apu->process; + + nes6502_setcontext (nsf->cpu); + + if (track > nsf->num_songs) + track = nsf->num_songs; + else if (track < 1) + track = 1; + + nsf->current_song = track; + + apu_reset (); + + nsf_inittune (nsf); +} + +void +nsf_setfilter (nsf_t * nsf, int filter_type) +{ + if (nsf) { + nsf_setcontext (nsf); + apu_setfilter (filter_type); + } +} + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.14 2000/07/05 14:54:45 matt +** fix for naughty Crystalis rip +** +** Revision 1.13 2000/07/04 04:59:38 matt +** removed DOS-specific stuff, fixed bug in address handlers +** +** Revision 1.12 2000/07/03 02:19:36 matt +** dynamic address range handlers, cleaner and faster +** +** Revision 1.11 2000/06/23 03:27:58 matt +** cleaned up external sound inteface +** +** Revision 1.10 2000/06/20 20:42:47 matt +** accuracy changes +** +** Revision 1.9 2000/06/20 00:05:58 matt +** changed to driver-based external sound generation +** +** Revision 1.8 2000/06/13 03:51:54 matt +** update API to take freq/sample data on nsf_playtrack +** +** Revision 1.7 2000/06/12 03:57:14 matt +** more robust checking for winamp plugin +** +** Revision 1.6 2000/06/12 01:13:00 matt +** added CPU/APU as members of the nsf struct +** +** Revision 1.5 2000/06/11 16:09:21 matt +** nsf_free is more robust +** +** Revision 1.4 2000/06/09 15:12:26 matt +** initial revision +** +*/ diff --git a/gst/nsf/nsf.h b/gst/nsf/nsf.h new file mode 100644 index 00000000..2f960367 --- /dev/null +++ b/gst/nsf/nsf.h @@ -0,0 +1,173 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** nsf.h +** +** NSF loading/saving related defines / prototypes +** $Id$ +*/ + +#ifndef _NSF_H_ +#define _NSF_H_ + +#include "osd.h" +#include "nes6502.h" +#include "nes_apu.h" + +#define NSF_MAGIC "NESM\x1A" + +#define NSF_DEDICATED_PAL 0x01 +#define NSF_DUAL_PAL_NTSC 0x02 + +#define EXT_SOUND_NONE 0x00 +#define EXT_SOUND_VRCVI 0x01 +#define EXT_SOUND_VRCVII 0x02 +#define EXT_SOUND_FDS 0x04 +#define EXT_SOUND_MMC5 0x08 +#define EXT_SOUND_NAMCO106 0x10 +#define EXT_SOUND_SUNSOFT_FME07 0x20 +/* bits 6,7: future expansion */ + +#define NSF_HEADER_SIZE 0x80 + +/* 60 Hertz refresh (NTSC) */ +#define NES_MASTER_CLOCK 21477272.7272 +#define NTSC_REFRESH 60 +#define NTSC_SUBCARRIER_DIV 12 +#define NTSC_SCANLINES 262 + +#define NES_FRAME_CYCLES ((NES_MASTER_CLOCK / NTSC_SUBCARRIER_DIV) / NTSC_REFRESH) +#define NES_SCANLINE_CYCLES (NES_FRAME_CYCLES / NTSC_SCANLINES) + +/* filter levels */ +enum +{ + NSF_FILTER_NONE, + NSF_FILTER_LOWPASS, + NSF_FILTER_WEIGHTED +}; + +typedef struct nsf_s +{ + /* NESM header */ + uint8 id[5] ; /* NESM\x1A */ + uint8 version ; /* spec version */ + uint8 num_songs ; /* total num songs */ + uint8 start_song ; /* first song */ + uint16 load_addr __PACKED__; /* loc to load code */ + uint16 init_addr __PACKED__; /* init call address */ + uint16 play_addr __PACKED__; /* play call address */ + uint8 song_name[32] ; /* name of song */ + uint8 artist_name[32] ; /* artist name */ + uint8 copyright[32] ; /* copyright info */ + uint16 ntsc_speed __PACKED__; /* playback speed (if NTSC) */ + uint8 bankswitch_info[8] ; /* initial code banking */ + uint16 pal_speed __PACKED__; /* playback speed (if PAL) */ + uint8 pal_ntsc_bits ; /* NTSC/PAL determination bits */ + uint8 ext_sound_type ; /* type of external sound gen. */ + uint8 reserved[4] ; /* reserved */ + + /* things that the NSF player needs */ + uint8 *data; /* actual NSF data */ + uint32 length; /* length of data */ + uint32 playback_rate; /* current playback rate */ + uint8 current_song; /* current song */ + boolean bankswitched; /* is bankswitched? */ + + /* CPU and APU contexts */ + nes6502_context *cpu; + apu_t *apu; + + /* our main processing routine, calls all external mixing routines */ + void (*process)(void *buffer, int num_samples); +} nsf_t; + +/* Function prototypes */ +extern void nsf_init(void); + +extern nsf_t *nsf_load(char *filename, void *source, int length); +extern void nsf_free(nsf_t **nsf_info); + +extern void nsf_playtrack(nsf_t *nsf, int track, int sample_rate, int sample_bits, + boolean stereo); +extern void nsf_frame(nsf_t *nsf); +extern void nsf_setchan(nsf_t *nsf, int chan, boolean enabled); +extern void nsf_setfilter(nsf_t *nsf, int filter_type); + +#endif /* _NSF_H_ */ + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.11 2000/07/04 04:59:24 matt +** removed DOS-specific stuff +** +** Revision 1.10 2000/07/03 02:19:36 matt +** dynamic address range handlers, cleaner and faster +** +** Revision 1.9 2000/06/23 03:27:58 matt +** cleaned up external sound inteface +** +** Revision 1.8 2000/06/20 04:04:37 matt +** moved external soundchip struct to apu module +** +** Revision 1.7 2000/06/20 00:05:45 matt +** changed to driver-based external sound generation +** +** Revision 1.6 2000/06/13 03:51:54 matt +** update API to take freq/sample data on nsf_playtrack +** +** Revision 1.5 2000/06/12 01:13:00 matt +** added CPU/APU as members of the nsf struct +** +** Revision 1.4 2000/06/09 15:12:26 matt +** initial revision +** +*/ diff --git a/gst/nsf/osd.h b/gst/nsf/osd.h new file mode 100644 index 00000000..0a2bb64a --- /dev/null +++ b/gst/nsf/osd.h @@ -0,0 +1,123 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** osd.h +** +** O/S dependent routine defintions (must be customized) +** $Id$ +*/ + +#ifndef _OSD_H_ +#define _OSD_H_ + + +#ifdef __GNUC__ +#define __PACKED__ __attribute__ ((packed)) +#define PATH_SEP '/' +#ifdef __DJGPP__ +#include <dpmi.h> +#include "dos_ints.h" +#endif +#elif defined(WIN32) +#define __PACKED__ +#define PATH_SEP '\\' +#else /* crapintosh? */ +#define __PACKED__ +#define PATH_SEP ':' +#endif + +extern void osd_loginit(void); +extern void osd_logshutdown(void); +extern void osd_logprint(const char *string); + +extern int osd_startsound(void (*playfunc)(void *buffer, int size)); +extern int osd_getsoundbps(void); +extern int osd_getsamplerate(void); + + +#ifndef NSF_PLAYER +#include "rgb.h" +#include "bitmap.h" + +extern bitmap_t *osd_getvidbuf(void); +typedef void (*blitproc_t)(bitmap_t *bmp, int x_pos, int y_pos, int width, int height); +extern blitproc_t osd_blit; +extern void osd_copytoscreen(void); + +extern void osd_showusage(char *filename); +extern void osd_fullname(char *fullname, const char *shortname); +extern char *osd_newextension(char *string, char *ext); + +extern void osd_setpalette(rgb_t *pal); +extern void osd_restorepalette(void); + +extern void osd_getinput(void); +extern int osd_gethostinput(void); +extern void osd_getmouse(int *x, int *y, int *button); + +extern int osd_init(void); +extern void osd_shutdown(void); +#endif /* !NSF_PLAYER */ + +#endif /* _OSD_H_ */ + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.7 2000/07/04 04:45:33 matt +** moved INLINE define into types.h +** +** Revision 1.6 2000/06/29 16:06:18 neil +** Wrapped DOS-specific headers in an ifdef +** +** Revision 1.5 2000/06/09 15:12:25 matt +** initial revision +** +*/ diff --git a/gst/nsf/types.h b/gst/nsf/types.h new file mode 100644 index 00000000..d1fa7498 --- /dev/null +++ b/gst/nsf/types.h @@ -0,0 +1,125 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** types.h +** +** Data type definitions +** $Id$ +*/ + +#ifndef _TYPES_H_ +#define _TYPES_H_ + +/* Define this if running on little-endian (x86) systems */ +#define HOST_LITTLE_ENDIAN + +#ifdef __GNUC__ +#define INLINE static inline +#elif defined(WIN32) +#define INLINE static __inline +#else /* crapintosh? */ +#define INLINE static +#endif + +/* These should be changed depending on the platform */ +typedef char int8; +typedef short int16; +typedef int int32; + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; + +typedef uint8 boolean; + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef NULL +#define NULL ((void *) 0) +#endif + +#ifdef NOFRENDO_DEBUG +#include <stdlib.h> +#include "memguard.h" +#include "log.h" +#define ASSERT(expr) if (FALSE == (expr))\ + {\ + log_printf("ASSERT: line %d of %s\n", __LINE__, __FILE__);\ + log_shutdown();\ + exit(1);\ + } +#define ASSERT_MSG(msg) {\ + log_printf("ASSERT: %s\n", msg);\ + log_shutdown();\ + exit(1);\ + } +#else /* Not debugging */ +#include "memguard.h" +#define ASSERT(expr) +#define ASSERT_MSG(msg) +#endif + +#endif /* _TYPES_H_ */ + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.7 2000/07/04 04:46:44 matt +** moved INLINE define from osd.h +** +** Revision 1.6 2000/06/09 15:12:25 matt +** initial revision +** +*/ diff --git a/gst/nsf/vrc7_snd.c b/gst/nsf/vrc7_snd.c new file mode 100644 index 00000000..07d27514 --- /dev/null +++ b/gst/nsf/vrc7_snd.c @@ -0,0 +1,376 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** vrc7_snd.c +** +** VRCVII sound hardware emulation +** Thanks to Charles MacDonald (cgfm2@hooked.net) for donating code. +** $Id$ +*/ + +#include <stdio.h> +#include "types.h" +#include "vrc7_snd.h" +#include "fmopl.h" + + +static int buflen; +static int16 *buffer; + +#define OPL_WRITE(opl, r, d) \ +{ \ + OPLWrite((opl)->ym3812, 0, (r)); \ + OPLWrite((opl)->ym3812, 1, (d)); \ +} + +static vrc7_t vrc7; + +/* Fixed instrument settings, from MAME's YM2413 emulation */ +/* This might need some tweaking... */ +unsigned char table[16][11] = { + /* 20 23 40 43 60 63 80 83 E0 E3 C0 */ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + + /* MAME */ + {0x01, 0x22, 0x23, 0x07, 0xF0, 0xF0, 0x07, 0x18, 0x00, 0x00, 0x00}, /* Violin */ + {0x23, 0x01, 0x68, 0x05, 0xF2, 0x74, 0x6C, 0x89, 0x00, 0x00, 0x00}, /* Acoustic Guitar(steel) */ + {0x13, 0x11, 0x25, 0x00, 0xD2, 0xB2, 0xF4, 0xF4, 0x00, 0x00, 0x00}, /* Acoustic Grand */ + {0x22, 0x21, 0x1B, 0x05, 0xC0, 0xA1, 0x18, 0x08, 0x00, 0x00, 0x00}, /* Flute */ + {0x22, 0x21, 0x2C, 0x03, 0xD2, 0xA1, 0x18, 0x57, 0x00, 0x00, 0x00}, /* Clarinet */ + {0x01, 0x22, 0xBA, 0x01, 0xF1, 0xF1, 0x1E, 0x04, 0x00, 0x00, 0x00}, /* Oboe */ + {0x21, 0x21, 0x28, 0x06, 0xF1, 0xF1, 0x6B, 0x3E, 0x00, 0x00, 0x00}, /* Trumpet */ + {0x27, 0x21, 0x60, 0x00, 0xF0, 0xF0, 0x0D, 0x0F, 0x00, 0x00, 0x00}, /* Church Organ */ + {0x20, 0x21, 0x2B, 0x06, 0x85, 0xF1, 0x6D, 0x89, 0x00, 0x00, 0x00}, /* French Horn */ + {0x01, 0x21, 0xBF, 0x02, 0x53, 0x62, 0x5F, 0xAE, 0x01, 0x00, 0x00}, /* Synth Voice */ + {0x23, 0x21, 0x70, 0x07, 0xD4, 0xA3, 0x4E, 0x64, 0x01, 0x00, 0x00}, /* Harpsichord */ + {0x2B, 0x21, 0xA4, 0x07, 0xF6, 0x93, 0x5C, 0x4D, 0x00, 0x00, 0x00}, /* Vibraphone */ + {0x21, 0x23, 0xAD, 0x07, 0x77, 0xF1, 0x18, 0x37, 0x00, 0x00, 0x00}, /* Synth Bass 1 */ + {0x21, 0x21, 0x2A, 0x03, 0xF3, 0xE2, 0x29, 0x46, 0x00, 0x00, 0x00}, /* Acoustic Bass */ + {0x21, 0x23, 0x37, 0x03, 0xF3, 0xE2, 0x29, 0x46, 0x00, 0x00, 0x00}, /* Electric Guitar(clean) */ + + +#if 0 + /* Horton, try 1 */ + {0x05, 0x03, 0x10, 0x06, 0x74, 0xA1, 0x13, 0xF4, 0x00, 0x00, 0x00}, + {0x05, 0x01, 0x16, 0x00, 0xF9, 0xA2, 0x15, 0xF5, 0x00, 0x00, 0x00}, + {0x01, 0x41, 0x11, 0x00, 0xA0, 0xA0, 0x83, 0x95, 0x00, 0x00, 0x00}, + {0x01, 0x41, 0x17, 0x00, 0x60, 0xF0, 0x83, 0x95, 0x00, 0x00, 0x00}, + {0x24, 0x41, 0x1F, 0x00, 0x50, 0xB0, 0x94, 0x94, 0x00, 0x00, 0x00}, + {0x05, 0x01, 0x0B, 0x04, 0x65, 0xA0, 0x54, 0x95, 0x00, 0x00, 0x00}, + {0x11, 0x41, 0x0E, 0x04, 0x70, 0xC7, 0x13, 0x10, 0x00, 0x00, 0x00}, + {0x02, 0x44, 0x16, 0x06, 0xE0, 0xE0, 0x31, 0x35, 0x00, 0x00, 0x00}, + {0x48, 0x22, 0x22, 0x07, 0x50, 0xA1, 0xA5, 0xF4, 0x00, 0x00, 0x00}, + {0x05, 0xA1, 0x18, 0x00, 0xA2, 0xA2, 0xF5, 0xF5, 0x00, 0x00, 0x00}, + {0x07, 0x81, 0x2B, 0x05, 0xA5, 0xA5, 0x03, 0x03, 0x00, 0x00, 0x00}, + {0x01, 0x41, 0x08, 0x08, 0xA0, 0xA0, 0x83, 0x95, 0x00, 0x00, 0x00}, + {0x21, 0x61, 0x12, 0x00, 0x93, 0x92, 0x74, 0x75, 0x00, 0x00, 0x00}, + {0x21, 0x62, 0x21, 0x00, 0x84, 0x85, 0x34, 0x15, 0x00, 0x00, 0x00}, + {0x21, 0x62, 0x0E, 0x00, 0xA1, 0xA0, 0x34, 0x15, 0x00, 0x00, 0x00}, +#endif + +#if 0 + /* Horton try 2 */ + {0x31, 0x22, 0x23, 0x07, 0xF0, 0xF0, 0xE8, 0xF7, 0x00, 0x00, 0x00}, + {0x03, 0x31, 0x68, 0x05, 0xF2, 0x74, 0x79, 0x9C, 0x00, 0x00, 0x00}, + {0x01, 0x51, 0x72, 0x04, 0xF1, 0xD3, 0x9D, 0x8B, 0x00, 0x00, 0x00}, + {0x22, 0x61, 0x1B, 0x05, 0xC0, 0xA1, 0xF8, 0xE8, 0x00, 0x00, 0x00}, + {0x22, 0x61, 0x2C, 0x03, 0xD2, 0xA1, 0xA7, 0xE8, 0x00, 0x00, 0x00}, + {0x31, 0x22, 0xFA, 0x01, 0xF1, 0xF1, 0xF4, 0xEE, 0x00, 0x00, 0x00}, + {0x21, 0x61, 0x28, 0x06, 0xF1, 0xF1, 0xCE, 0x9B, 0x00, 0x00, 0x00}, + {0x27, 0x61, 0x60, 0x00, 0xF0, 0xF0, 0xFF, 0xFD, 0x00, 0x00, 0x00}, + {0x60, 0x21, 0x2B, 0x06, 0x85, 0xF1, 0x79, 0x9D, 0x00, 0x00, 0x00}, + {0x31, 0xA1, 0xFF, 0x0A, 0x53, 0x62, 0x5E, 0xAF, 0x00, 0x00, 0x00}, + {0x03, 0xA1, 0x70, 0x0F, 0xD4, 0xA3, 0x94, 0xBE, 0x00, 0x00, 0x00}, + {0x2B, 0x61, 0xE4, 0x07, 0xF6, 0x93, 0xBD, 0xAC, 0x00, 0x00, 0x00}, + {0x21, 0x63, 0xED, 0x07, 0x77, 0xF1, 0xC7, 0xE8, 0x00, 0x00, 0x00}, + {0x21, 0x61, 0x2A, 0x03, 0xF3, 0xE2, 0xB6, 0xD9, 0x00, 0x00, 0x00}, + {0x21, 0x63, 0x37, 0x03, 0xF3, 0xE2, 0xB6, 0xD9, 0x00, 0x00, 0x00}, +#endif +}; + +static void +vrc7_reset (void) +{ + int n; + + /* Point to current VRC7 context */ + vrc7_t *opll = &vrc7; + + /* Clear all YM3812 registers */ + for (n = 0; n < 0x100; n++) + OPL_WRITE (opll, n, 0x00); + + /* Turn off rhythm mode and key-on bits */ + OPL_WRITE (opll, 0xBD, 0xC0); + + /* Enable waveform select */ + OPL_WRITE (opll, 0x01, 0x20); +} + +static void +vrc7_init (void) +{ + vrc7.ym3812 = + OPLCreate (OPL_TYPE_YM3812, 3579545, apu_getcontext ()->sample_rate); + ASSERT (vrc7.ym3812); + buflen = apu_getcontext ()->num_samples; + buffer = malloc (buflen * 2); + ASSERT (buffer); + vrc7_reset (); +} + +static void +vrc7_shutdown (void) +{ + void *t = buffer; + + vrc7_reset (); + OPLDestroy (vrc7.ym3812); + free (t); +} + +/* channel (0-9), instrument (0-F), volume (0-3F, YM3812 format) */ +static void +load_instrument (uint8 ch, uint8 inst, uint8 vol) +{ + /* Point to current VRC7 context */ + vrc7_t *opll = &vrc7; + + /* Point to fixed instrument or user table */ + uint8 *param = (inst == 0) ? &opll->user[0] : &table[inst][0]; + + /* Maps channels to operator registers */ + uint8 ch2op[] = { 0, 1, 2, 8, 9, 10, 16, 17, 18 }; + + /* Make operator offset from requested channel */ + uint8 op = ch2op[ch]; + + /* Store volume level */ + opll->channel[ch].volume = (vol & 0x3F); + + /* Store instrument number */ + opll->channel[ch].instrument = (inst & 0x0F); + + /* Update instrument settings, except frequency registers */ + OPL_WRITE (opll, 0x20 + op, param[0]); + OPL_WRITE (opll, 0x23 + op, param[1]); + OPL_WRITE (opll, 0x40 + op, param[2]); + OPL_WRITE (opll, 0x43 + op, (param[3] & 0xC0) | opll->channel[ch].volume); + OPL_WRITE (opll, 0x60 + op, param[4]); + OPL_WRITE (opll, 0x63 + op, param[5]); + OPL_WRITE (opll, 0x80 + op, param[6]); + OPL_WRITE (opll, 0x83 + op, param[7]); + OPL_WRITE (opll, 0xE0 + op, param[8]); + OPL_WRITE (opll, 0xE3 + op, param[9]); + OPL_WRITE (opll, 0xC0 + ch, param[10]); +} + +static void +vrc7_write (uint32 address, uint8 data) +{ + /* Point to current VRC7 context */ + vrc7_t *opll = &vrc7; + + if (address & 0x0020) { /* data port */ + /* Store register data */ + opll->reg[opll->latch] = data; + + switch (opll->latch & 0x30) { + case 0x00: /* User instrument registers */ + switch (opll->latch & 0x0F) { + case 0x00: /* Misc. ctrl. (modulator) */ + case 0x01: /* Misc. ctrl. (carrier) */ + case 0x02: /* Key scale level and total level (modulator) */ + case 0x04: /* Attack / Decay (modulator) */ + case 0x05: /* Attack / Decay (carrier) */ + case 0x06: /* Sustain / Release (modulator) */ + case 0x07: /* Sustain / Release (carrier) */ + opll->user[(opll->latch & 0x07)] = data; + break; + + case 0x03: /* Key scale level, carrier/modulator waveform, feedback */ + + /* Key scale level (carrier) */ + /* Don't touch the total level (channel volume) */ + opll->user[3] = (opll->user[3] & 0x3F) | (data & 0xC0); + + /* Waveform select for the modulator */ + opll->user[8] = (data >> 3) & 1; + + /* Waveform select for the carrier */ + opll->user[9] = (data >> 4) & 1; + + /* Store feedback level in YM3812 format */ + opll->user[10] = ((data & 0x07) << 1) & 0x0E; + break; + } + + /* If the user instrument registers were accessed, then + go through each channel and update the ones that were + currently using the user instrument. We can skip the + last three channels in rhythm mode since they can + only use percussion sounds anyways. */ + if (opll->latch <= 0x05) { + uint8 x; + + for (x = 0; x < 6; x++) + if (opll->channel[x].instrument == 0x00) + load_instrument (x, 0x00, opll->channel[x].volume); + } + break; + + case 0x10: /* Channel Frequency (LSB) */ + case 0x20: /* Channel Frequency (MSB) + key-on and sustain control */ + { + uint8 block; + uint16 frequency; + uint8 ch = (opll->latch & 0x0F); + + /* Ensure proper channel range */ + if (ch > 0x05) + break; + + /* Get VRC7 channel frequency */ + frequency = + ((opll->reg[0x10 + ch] & 0xFF) | ((opll->reg[0x20 + + ch] & 0x01) << 8)); + + /* Scale 9 bit frequency to 10 bits */ + frequency = (frequency << 1) & 0x1FFF; + + /* Get VRC7 block */ + block = (opll->reg[0x20 + ch] >> 1) & 7; + + /* Add in block */ + frequency |= (block << 10); + + /* Add key-on flag */ + if (opll->reg[0x20 + ch] & 0x10) + frequency |= 0x2000; + + /* Save current frequency/block/key-on setting */ + opll->channel[ch].frequency = (frequency & 0x3FFF); + + /* Write changes to YM3812 */ + OPL_WRITE (opll, 0xA0 + ch, (opll->channel[ch].frequency >> 0) & 0xFF); + OPL_WRITE (opll, 0xB0 + ch, (opll->channel[ch].frequency >> 8) & 0xFF); + } + break; + + case 0x30: /* Channel Volume Level and Instrument Select */ + + /* Ensure proper channel range */ + if (opll->latch > 0x35) + break; + + { + uint8 ch = (opll->latch & 0x0F); + uint8 inst = (data >> 4) & 0x0F; + uint8 vol = (data & 0x0F) << 2; + + load_instrument (ch, inst, vol); + } + + break; + } + } else { /* Register latch */ + + opll->latch = (data & 0x3F); + } +} + +static int32 +vrc7_process (void) +{ + static int sample = 0; + + /* update a large chunk at once */ + if (sample >= buflen) { + sample -= buflen; + YM3812UpdateOne (vrc7.ym3812, buffer, buflen); + } + + return (int32) ((int16 *) buffer)[sample++]; +} + +static apu_memwrite vrc7_memwrite[] = { + {0x9010, 0x9010, vrc7_write}, + {0x9030, 0x9030, vrc7_write}, + {-1, -1, NULL} +}; + +apuext_t vrc7_ext = { + vrc7_init, + vrc7_shutdown, + vrc7_reset, + vrc7_process, + NULL, /* no reads */ + vrc7_memwrite +}; + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.5 2000/07/04 04:51:02 matt +** made data types stricter +** +** Revision 1.4 2000/07/03 02:18:53 matt +** much better external module exporting +** +** Revision 1.3 2000/06/20 20:45:09 matt +** minor cleanups +** +** Revision 1.2 2000/06/20 04:06:16 matt +** migrated external sound definition to apu module +** +** Revision 1.1 2000/06/20 00:06:47 matt +** initial revision +** +*/ diff --git a/gst/nsf/vrc7_snd.h b/gst/nsf/vrc7_snd.h new file mode 100644 index 00000000..9521d4f7 --- /dev/null +++ b/gst/nsf/vrc7_snd.h @@ -0,0 +1,99 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** vrc7_snd.h +** +** VRCVII (Konami MMC) sound hardware emulation header +** Thanks to Charles MacDonald (cgfm2@hooked.net) for donating code. +** +** $Id$ +*/ + +#ifndef _VRC7_SND_H_ +#define _VRC7_SND_H_ + +#include "fmopl.h" + +/* VRC7 context */ +typedef struct vrc7_s +{ + uint8 reg[0x40]; /* 64 registers */ + uint8 latch; /* Register latch */ + uint8 user[0x10]; /* User instrument settings */ + struct + { + uint16 frequency; /* Channel frequency */ + uint8 volume; /* Channel volume */ + uint8 instrument; /* Channel instrument */ + } channel[9]; + + FM_OPL *ym3812; +} vrc7_t; + + +#include "nes_apu.h" + +extern apuext_t vrc7_ext; + +#endif /* !_VRC7_SND_H_ */ + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.3 2000/07/04 04:51:02 matt +** made data types stricter +** +** Revision 1.2 2000/06/20 04:06:16 matt +** migrated external sound definition to apu module +** +** Revision 1.1 2000/06/20 00:06:47 matt +** initial revision +** +*/ diff --git a/gst/nsf/vrcvisnd.c b/gst/nsf/vrcvisnd.c new file mode 100644 index 00000000..e6b5eb3e --- /dev/null +++ b/gst/nsf/vrcvisnd.c @@ -0,0 +1,241 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** vrcvisnd.c +** +** VRCVI sound hardware emulation +** $Id$ +*/ + +#include "types.h" +#include "vrcvisnd.h" +#include "nes_apu.h" + + +static vrcvisnd_t vrcvi; +static int32 vrcvi_incsize; + +/* VRCVI rectangle wave generation */ +static int32 +vrcvi_rectangle (vrcvirectangle_t * chan) +{ + /* reg0: 0-3=volume, 4-6=duty cycle + ** reg1: 8 bits of freq + ** reg2: 0-3=high freq, 7=enable + */ + + chan->phaseacc -= vrcvi_incsize; /* # of clocks per wave cycle */ + while (chan->phaseacc < 0) { + chan->phaseacc += chan->freq; + chan->adder = (chan->adder + 1) & 0x0F; + } + + /* return if not enabled */ + if (FALSE == chan->enabled) + return 0; + + if (chan->adder < chan->duty_flip) + return -(chan->volume); + else + return chan->volume; +} + +/* VRCVI sawtooth wave generation */ +static int32 +vrcvi_sawtooth (vrcvisawtooth_t * chan) +{ + /* reg0: 0-5=phase accumulator bits + ** reg1: 8 bits of freq + ** reg2: 0-3=high freq, 7=enable + */ + + chan->phaseacc -= vrcvi_incsize; /* # of clocks per wav cycle */ + while (chan->phaseacc < 0) { + chan->phaseacc += chan->freq; + chan->output_acc += chan->volume; + + if (7 == ++chan->adder) { + chan->adder = 0; + chan->output_acc = 0; + } + } + + /* return if not enabled */ + if (FALSE == chan->enabled) + return 0; + else + return (chan->output_acc >> 3) << 9; +} + +/* mix vrcvi sound channels together */ +static int32 +vrcvi_process (void) +{ + int32 output; + + output = vrcvi_rectangle (&vrcvi.rectangle[0]); + output += vrcvi_rectangle (&vrcvi.rectangle[1]); + output += vrcvi_sawtooth (&vrcvi.saw); + + return output; +} + +/* write to registers */ +static void +vrcvi_write (uint32 address, uint8 value) +{ + int chan; + + switch (address & 0xB003) { + case 0x9000: + case 0xA000: + chan = (address >> 12) - 9; + vrcvi.rectangle[chan].reg[0] = value; + vrcvi.rectangle[chan].volume = (value & 0x0F) << 8; + vrcvi.rectangle[chan].duty_flip = (value >> 4) + 1; + break; + case 0x9001: + case 0xA001: + chan = (address >> 12) - 9; + vrcvi.rectangle[chan].reg[1] = value; + vrcvi.rectangle[chan].freq = + APU_TO_FIXED (((vrcvi.rectangle[chan].reg[2] & 0x0F) << 8) + value + + 1); + break; + case 0x9002: + case 0xA002: + chan = (address >> 12) - 9; + vrcvi.rectangle[chan].reg[2] = value; + vrcvi.rectangle[chan].freq = + APU_TO_FIXED (((value & 0x0F) << 8) + vrcvi.rectangle[chan].reg[1] + + 1); + vrcvi.rectangle[chan].enabled = (value & 0x80) ? TRUE : FALSE; + break; + case 0xB000: + vrcvi.saw.reg[0] = value; + vrcvi.saw.volume = value & 0x3F; + break; + case 0xB001: + vrcvi.saw.reg[1] = value; + vrcvi.saw.freq = + APU_TO_FIXED ((((vrcvi.saw.reg[2] & 0x0F) << 8) + value + 1) << 1); + break; + case 0xB002: + vrcvi.saw.reg[2] = value; + vrcvi.saw.freq = + APU_TO_FIXED ((((value & 0x0F) << 8) + vrcvi.saw.reg[1] + 1) << 1); + vrcvi.saw.enabled = (value & 0x80) ? TRUE : FALSE; + break; + default: + break; + } +} + +/* reset state of vrcvi sound channels */ +static void +vrcvi_reset (void) +{ + int i; + + /* preload regs */ + for (i = 0; i < 3; i++) { + vrcvi_write (0x9000 + i, 0); + vrcvi_write (0xA000 + i, 0); + vrcvi_write (0xB000 + i, 0); + } + + /* get the phase period from the apu */ + vrcvi_incsize = apu_getcyclerate (); +} + +static void +vrcvi_dummy (void) +{ +} + +static apu_memwrite vrcvi_memwrite[] = { + /* { 0x4040, 0x4092, ext_write }, *//* FDS sound regs */ + {0x9000, 0x9002, vrcvi_write}, /* vrc6 */ + {0xA000, 0xA002, vrcvi_write}, + {0xB000, 0xB002, vrcvi_write}, + {-1, -1, NULL} +}; + +apuext_t vrcvi_ext = { + vrcvi_dummy, /* no init */ + vrcvi_dummy, /* no shutdown */ + vrcvi_reset, + vrcvi_process, + NULL, /* no reads */ + vrcvi_memwrite +}; + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.9 2000/07/04 04:51:41 matt +** cleanups +** +** Revision 1.8 2000/07/03 02:18:53 matt +** much better external module exporting +** +** Revision 1.7 2000/06/20 04:06:16 matt +** migrated external sound definition to apu module +** +** Revision 1.6 2000/06/20 00:08:58 matt +** changed to driver based API +** +** Revision 1.5 2000/06/09 16:49:02 matt +** removed all floating point from sound generation +** +** Revision 1.4 2000/06/09 15:12:28 matt +** initial revision +** +*/ diff --git a/gst/nsf/vrcvisnd.h b/gst/nsf/vrcvisnd.h new file mode 100644 index 00000000..d141aef7 --- /dev/null +++ b/gst/nsf/vrcvisnd.h @@ -0,0 +1,112 @@ +/* +** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) +** +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of version 2 of the GNU Library General +** Public License 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 +** Library General Public License for more details. To obtain a +** copy of the GNU Library General Public License, write to the Free +** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** Any permitted reproduction of these routines, in whole or in part, +** must bear this legend. +** +** +** vrcvisnd.h +** +** VRCVI (Konami MMC) sound hardware emulation header +** $Id$ +*/ + +#ifndef _VRCVISND_H_ +#define _VRCVISND_H_ + +typedef struct vrcvirectangle_s +{ + uint8 reg[3]; + int32 phaseacc; + uint8 adder; + + int32 freq; + int32 volume; + uint8 duty_flip; + boolean enabled; +} vrcvirectangle_t; + +typedef struct vrcvisawtooth_s +{ + uint8 reg[3]; + int32 phaseacc; + uint8 adder; + uint8 output_acc; + + int32 freq; + uint8 volume; + boolean enabled; +} vrcvisawtooth_t; + +typedef struct vrcvisnd_s +{ + vrcvirectangle_t rectangle[2]; + vrcvisawtooth_t saw; +} vrcvisnd_t; + +#include "nes_apu.h" + +extern apuext_t vrcvi_ext; + +#endif /* _VRCVISND_H_ */ + +/* +** $Log$ +** Revision 1.1 2006/07/13 15:07:28 wtay +** Based on patches by: Johan Dahlin <johan at gnome dot org> +** Ronald Bultje <rbultje at ronald dot bitfreak dot net> +** * configure.ac: +** * gst/nsf/Makefile.am: +** * gst/nsf/dis6502.h: +** * gst/nsf/fds_snd.c: +** * gst/nsf/fds_snd.h: +** * gst/nsf/fmopl.c: +** * gst/nsf/fmopl.h: +** * gst/nsf/gstnsf.c: +** * gst/nsf/gstnsf.h: +** * gst/nsf/log.c: +** * gst/nsf/log.h: +** * gst/nsf/memguard.c: +** * gst/nsf/memguard.h: +** * gst/nsf/mmc5_snd.c: +** * gst/nsf/mmc5_snd.h: +** * gst/nsf/nes6502.c: +** * gst/nsf/nes6502.h: +** * gst/nsf/nes_apu.c: +** * gst/nsf/nes_apu.h: +** * gst/nsf/nsf.c: +** * gst/nsf/nsf.h: +** * gst/nsf/osd.h: +** * gst/nsf/types.h: +** * gst/nsf/vrc7_snd.c: +** * gst/nsf/vrc7_snd.h: +** * gst/nsf/vrcvisnd.c: +** * gst/nsf/vrcvisnd.h: +** Added NSF decoder plugin. Fixes 151192. +** +** Revision 1.7 2000/06/20 04:06:16 matt +** migrated external sound definition to apu module +** +** Revision 1.6 2000/06/20 00:08:58 matt +** changed to driver based API +** +** Revision 1.5 2000/06/09 16:49:02 matt +** removed all floating point from sound generation +** +** Revision 1.4 2000/06/09 15:12:28 matt +** initial revision +** +*/ + |