diff options
Diffstat (limited to 'gst/nsf/nsf.c')
-rw-r--r-- | gst/nsf/nsf.c | 648 |
1 files changed, 648 insertions, 0 deletions
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 +** +*/ |