diff options
Diffstat (limited to 'contrib/nosefart/nsf.c')
-rw-r--r-- | contrib/nosefart/nsf.c | 1055 |
1 files changed, 1055 insertions, 0 deletions
diff --git a/contrib/nosefart/nsf.c b/contrib/nosefart/nsf.c new file mode 100644 index 000000000..69f77546b --- /dev/null +++ b/contrib/nosefart/nsf.c @@ -0,0 +1,1055 @@ +/* +** 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: nsf.c,v 1.4 2006/09/26 00:52:17 dgp85 Exp $ +*/ + + +#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) +{ + nes6502_chk_mem_access(&cur_nsf->cpu->acc_mem_page[0][address & 0x7FF], + NES6502_READ_ACCESS); + return cur_nsf->cpu->mem_page[0][address & 0x7FF]; +} + +static void write_mirrored_ram(uint32 address, uint8 value) +{ + nes6502_chk_mem_access(&cur_nsf->cpu->acc_mem_page[0][address & 0x7FF], + NES6502_WRITE_ACCESS); + 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; + int roffset; + uint8 *offset; + + cpu_page = address & 0x0F; + roffset = -(cur_nsf->load_addr & 0x0FFF) + ((int)value << 12); + offset = cur_nsf->data + roffset; + + nes6502_getcontext(cur_nsf->cpu); + cur_nsf->cpu->mem_page[cpu_page] = offset; +#ifdef NES6502_MEM_ACCESS_CTRL + cur_nsf->cpu->acc_mem_page[cpu_page] = offset + cur_nsf->length; +#endif + 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); + +#ifdef NES6502_MEM_ACCESS_CTRL + memset(nsf->cpu->acc_mem_page[0], 0, 0x800); + memset(nsf->cpu->acc_mem_page[6], 0, 0x1000); + memset(nsf->cpu->acc_mem_page[7], 0, 0x1000); + memset(nsf->data+nsf->length, 0, nsf->length); +#endif + nsf->cur_frame = 0; +/* nsf->last_access_frame = 0; */ + nsf->cur_frame_end = !nsf->song_frames + ? 0 + : nsf->song_frames[nsf->current_song]; + + 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) +{ + // This is how Matthew Conte left it + //nsf_setcontext(nsf); /* future expansion =) */ + + // This was suggested by Arne Morten Kvarving, who says: +/* Also, I fixed a bug that prevented Nosefart to play multiple tunes at + one time (actually it was just a few missing setcontext calls in the + playback routine, it had a nice TODO commented beside it. You had to set + the cpu and apu contexts not just the nsf context). + + it will affect any player that tries to use nosefart to play more than one + tune at a time. +*/ + nsf_setcontext(nsf); + apu_setcontext(nsf->apu); + nes6502_setcontext(nsf->cpu); + + /* one frame of NES processing */ + nsf_setup_routine(nsf->play_addr, 0, 0); + nes6502_execute((int) NES_FRAME_CYCLES); + + ++nsf->cur_frame; +#if defined(NES6502_MEM_ACCESS_CTRL) && 0 + if (nes6502_mem_access) { + uint32 sec = + (nsf->last_access_frame + nsf->playback_rate - 1) / nsf->playback_rate; + nsf->last_access_frame = nsf->cur_frame; + fprintf(stderr,"nsf : memory access [%x] at frame #%u [%u:%02u]\n", + nes6502_mem_access, + nsf->last_access_frame, + sec/60, sec%60); + } +#endif + +} + +/* Deallocate memory */ +void nes_shutdown(nsf_t *nsf) +{ + int i; + + ASSERT(nsf); + + if (nsf->cpu) + { + if (nsf->cpu->mem_page[0]) + { + free(nsf->cpu->mem_page[0]);/*tracks 1 and 2 of lifeforce hang here.*/ + } + for (i = 5; i <= 7; i++) { + if (nsf->cpu->mem_page[i]) + { + free(nsf->cpu->mem_page[i]); + } + } + +#ifdef NES6502_MEM_ACCESS_CTRL + if (nsf->cpu->acc_mem_page[0]) + { + free(nsf->cpu->acc_mem_page[0]); + } + for (i = 5; i <= 7; i++) { + if (nsf->cpu->acc_mem_page[i]) + { + free(nsf->cpu->acc_mem_page[i]); + } + } +#endif + free(nsf->cpu); + } +} + +int nsf_init(void) +{ + nes6502_init(); + return 0; +} + +/* 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; + } + +#ifdef NES6502_MEM_ACCESS_CTRL + nsf->cpu->acc_mem_page[0] = malloc(0x800); + if (NULL == nsf->cpu->acc_mem_page[0]) + return -1; + /* allocate some space for the NSF "player" MMC5 EXRAM, and WRAM */ + for (i = 5; i <= 7; i++) + { + nsf->cpu->acc_mem_page[i] = malloc(0x1000); + if (NULL == nsf->cpu->acc_mem_page[i]) + return -1; + } +#endif + + nsf->cpu->read_handler = nsf_readhandler; + nsf->cpu->write_handler = nsf_writehandler; + + return 0; +} + +static unsigned int nsf_playback_rate(nsf_t *nsf) +{ + 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 */ + } + return 0; +} + +static void nsf_setup(nsf_t *nsf) +{ + int i; + + nsf->current_song = nsf->start_song; + nsf_playback_rate(nsf); + + 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 */ + +/* $$$ ben : find extension. Should be OK with DOS, but not with some + * OS like RiscOS ... */ +static char * find_ext(char *fn) +{ + char * a, * b, * c; + a = strrchr(fn,'.'); + b = strrchr(fn,'/'); + c = strrchr(fn,'\\'); + if (a <= b || a <= c) { + a = 0; + } + return a; +} + +/* $$$ ben : FILE loader */ +struct nsf_file_loader_t { + struct nsf_loader_t loader; + FILE *fp; + char * fname; + int name_allocated; +}; + +static int nfs_open_file(struct nsf_loader_t *loader) +{ + struct nsf_file_loader_t * floader = (struct nsf_file_loader_t *)loader; + + floader->name_allocated = 0; + floader->fp = 0; + if (!floader->fname) { + return -1; + } + floader->fp = fopen(floader->fname,"rb"); + if (!floader->fp) { + char * fname, * ext; + ext = find_ext(floader->fname); + if (ext) { + /* There was an extension, so we do not change it */ + return -1; + } + fname = malloc(strlen(floader->fname) + 5); + if (!fname) { + return -1; + } + /* try with .nsf extension. */ + strcpy(fname, floader->fname); + strcat(fname, ".nsf"); + floader->fp = fopen(fname,"rb"); + if (!floader->fp) { + free(fname); + return -1; + } + floader->fname = fname; + floader->name_allocated = 1; + } + return 0; +} + +static void nfs_close_file(struct nsf_loader_t *loader) +{ + struct nsf_file_loader_t * floader = (struct nsf_file_loader_t *)loader; + if (floader->fp) { + fclose(floader->fp); + floader->fp = 0; + } + if (floader->fname && floader->name_allocated) { + free(floader->fname); + floader->fname = 0; + floader->name_allocated = 0; + } +} + +static int nfs_read_file(struct nsf_loader_t *loader, void *data, int n) +{ + struct nsf_file_loader_t * floader = (struct nsf_file_loader_t *)loader; + int r = fread(data, 1, n, floader->fp); + if (r >= 0) { + r = n-r; + } + return r; +} + +static int nfs_length_file(struct nsf_loader_t *loader) +{ + struct nsf_file_loader_t * floader = (struct nsf_file_loader_t *)loader; + long save, pos; + save = ftell(floader->fp); + fseek(floader->fp, 0, SEEK_END); + pos = ftell(floader->fp); + fseek(floader->fp, save, SEEK_SET); + return pos; +} + +static int nfs_skip_file(struct nsf_loader_t *loader, int n) +{ + struct nsf_file_loader_t * floader = (struct nsf_file_loader_t *)loader; + int r; + r = fseek(floader->fp, n, SEEK_CUR); + return r; +} + +static const char * nfs_fname_file(struct nsf_loader_t *loader) +{ + struct nsf_file_loader_t * floader = (struct nsf_file_loader_t *)loader; + return floader->fname ? floader->fname : "<null>"; +} + +static struct nsf_file_loader_t nsf_file_loader = { + { + nfs_open_file, + nfs_close_file, + nfs_read_file, + nfs_length_file, + nfs_skip_file, + nfs_fname_file + }, + 0,0,0 +}; + +struct nsf_mem_loader_t { + struct nsf_loader_t loader; + uint8 *data; + unsigned long cur; + unsigned long len; + char fname[32]; +}; + +static int nfs_open_mem(struct nsf_loader_t *loader) +{ + struct nsf_mem_loader_t * mloader = (struct nsf_mem_loader_t *)loader; + if (!mloader->data) { + return -1; + } + mloader->cur = 0; + sprintf(mloader->fname,"<mem(%p,%u)>", + mloader->data, (unsigned int)mloader->len); + return 0; +} + +static void nfs_close_mem(struct nsf_loader_t *loader) +{ + struct nsf_mem_loader_t * mloader = (struct nsf_mem_loader_t *)loader; + mloader->data = 0; + mloader->cur = 0; + mloader->len = 0; +} + +static int nfs_read_mem(struct nsf_loader_t *loader, void *data, int n) +{ + struct nsf_mem_loader_t * mloader = (struct nsf_mem_loader_t *)loader; + int rem; + if (n <= 0) { + return n; + } + if (!mloader->data) { + return -1; + } + rem = mloader->len - mloader->cur; + if (rem > n) { + rem = n; + } + memcpy(data, mloader->data + mloader->cur, rem); + mloader->cur += rem; + return n - rem; +} + +static int nfs_length_mem(struct nsf_loader_t *loader) +{ + struct nsf_mem_loader_t * mloader = (struct nsf_mem_loader_t *)loader; + return mloader->len; +} + +static int nfs_skip_mem(struct nsf_loader_t *loader, int n) +{ + struct nsf_mem_loader_t * mloader = (struct nsf_mem_loader_t *)loader; + unsigned long goal = mloader->cur + n; + mloader->cur = (goal > mloader->len) ? mloader->len : goal; + return goal - mloader->cur; +} + +static const char * nfs_fname_mem(struct nsf_loader_t *loader) +{ + struct nsf_mem_loader_t * mloader = (struct nsf_mem_loader_t *)loader; + return mloader->fname; +} + +static struct nsf_mem_loader_t nsf_mem_loader = { + { nfs_open_mem, nfs_close_mem, nfs_read_mem, nfs_length_mem, nfs_skip_mem }, + 0,0,0 +}; + +nsf_t * nsf_load_extended(struct nsf_loader_t * loader) +{ + nsf_t *temp_nsf = 0; + int length; + char id[6]; + + struct { + uint8 magic[4]; /* always "NESM" */ + uint8 type[4]; /* defines extension type */ + uint8 size[4]; /* extension data size (this struct include) */ + } nsf_file_ext; + + /* no loader ! */ + if (!loader) { + return NULL; + } + + /* Open the "file" */ + if (loader->open(loader) < 0) { + return NULL; + } + + /* Get file size, and exit if there is not enough data for NSF header + * and more since it does not make sens to have header without data. + */ + length = loader->length(loader); + /* For version 2, we do not need file length. just check error later. */ +#if 0 + if (length <= NSF_HEADER_SIZE) { + log_printf("nsf : [%s] not an NSF format file\n", + loader->fname); + goto error; + } +#endif + + /* Read magic */ + if (loader->read(loader, id, 5)) { + log_printf("nsf : [%s] error reading magic number\n", + loader->fname); + goto error; + } + + /* Check magic */ + if (memcmp(id, NSF_MAGIC, 5)) { + log_printf("nsf : [%s] is not an NSF format file\n", + loader->fname); + goto error; + } + + /* $$$ ben : Now the file should be an NSF, we can start allocating. + * first : the nsf struct + */ + temp_nsf = malloc(sizeof(nsf_t)); + + if (NULL == temp_nsf) { + log_printf("nsf : [%s] error allocating nsf header\n", + loader->fname); + goto error; + } + /* $$$ ben : safety net */ + memset(temp_nsf,0,sizeof(nsf_t)); + /* Copy magic ID */ + memcpy(temp_nsf,id,5); + + /* Read header (without MAGIC) */ + if (loader->read(loader, (int8 *)temp_nsf+5, NSF_HEADER_SIZE - 5)) { + log_printf("nsf : [%s] error reading nsf header\n", + loader->fname); + goto error; + } + + /* 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 */ + + + /* Here comes the specific codes for spec version 2 */ + + temp_nsf->length = 0; + + if (temp_nsf->version > 1) { + /* Get specified data size in reserved field (3 bytes). */ + temp_nsf->length = 0 + + temp_nsf->reserved[0] + + (temp_nsf->reserved[1]<<8) + + (temp_nsf->reserved[2]<<16); + + } + /* no specified size : try to guess with file length. */ + if (!temp_nsf->length) { + temp_nsf->length = length - NSF_HEADER_SIZE; + } + + if (temp_nsf->length <= 0) { + log_printf("nsf : [%s] not an NSF format file (missing data)\n", + loader->fname); + goto error; + } + + /* Allocate NSF space, and load it up! */ + { + int len = temp_nsf->length; +#ifdef NES6502_MEM_ACCESS_CTRL + /* $$$ twice memory for access control shadow mem. */ + len <<= 1; +#endif + temp_nsf->data = malloc(len); + } + if (NULL == temp_nsf->data) { + log_printf("nsf : [%s] error allocating nsf data\n", + loader->fname); + goto error; + } + + /* Read data */ + if (loader->read(loader, temp_nsf->data, temp_nsf->length)) { + log_printf("nsf : [%s] error reading NSF data\n", + loader->fname); + goto error; + } + + /* Here comes the second part of spec > 1 : get extension */ + while (!loader->read(loader, &nsf_file_ext, sizeof(nsf_file_ext)) + && !memcmp(nsf_file_ext.magic,id,4)) { + /* Got a NESM extension here. Checks for known extension type : + * right now, the only extension is "TIME" which give songs length. + * in frames. + */ + int size; + size = 0 + + nsf_file_ext.size[0] + + (nsf_file_ext.size[1] << 8) + + (nsf_file_ext.size[2] << 16) + + (nsf_file_ext.size[3] << 24); + + if (size < sizeof(nsf_file_ext)) { + log_printf("nsf : [%s] corrupt extension size (%d)\n", + loader->fname, size); + /* Not a fatal error here. Just skip extension loading. */ + break; + } + size -= sizeof(nsf_file_ext); + + if (!temp_nsf->song_frames + && !memcmp(nsf_file_ext.type,"TIME", 4) + && !(size & 3) + && (size >= 2*4) + && (size <= 256*4)) { + + uint8 tmp_time[256][4]; + int tsongs = size >> 2; + int i; + int songs = temp_nsf->num_songs; + + /* Add 1 for 0 which contains total time for all songs. */ + ++songs; + + if (loader->read(loader, tmp_time, size)) { + log_printf("nsf : [%s] missing extension data\n", + loader->fname); + /* Not a fatal error here. Just skip extension loading. */ + break; + } + /* Alloc song_frames for songs (not tsongs). */ + temp_nsf->song_frames = malloc(sizeof(*temp_nsf->song_frames) * songs); + if (!temp_nsf->song_frames) { + log_printf("nsf : [%s] extension alloc failed\n", + loader->fname); + /* Not a fatal error here. Just skip extension loading. */ + break; + } + + if (tsongs > songs) { + tsongs = songs; + } + + /* Copy time info. */ + for (i=0; i<tsongs; ++i) { + temp_nsf->song_frames[i] = 0 + | tmp_time[i][0] + | (tmp_time[i][1] << 8) + | (tmp_time[i][2] << 16) + | (tmp_time[i][2] << 24); + } + /* Clear missing (safety net). */ + for (; i<songs; ++i) { + temp_nsf->song_frames[i] = 0; + } + } else if (loader->skip(loader, size)) { + log_printf("nsf : [%s] extension skip failed\n", + loader->fname); + /* Not a fatal error here. Just skip extension loading. */ + break; + } + } + + + /* Close "file" */ + loader->close(loader); + loader = 0; + + /* Set up some variables */ + nsf_setup(temp_nsf); + temp_nsf->apu = NULL; /* just make sure */ + + if (nsf_cpuinit(temp_nsf)) { + log_printf("nsf : error cpu init\n"); + goto error; + } + return temp_nsf; + + /* $$$ ben : some people tell that goto are not clean. I am not agree with + * them. In most case, it allow to avoid code duplications, which are as + * most people know a source of error... Here we are sure of being clean + */ + error: + if (loader) { + loader->close(loader); + } + if (temp_nsf) { + nsf_free(&temp_nsf); + } + return 0; +} + +/* Load a ROM image into memory */ +nsf_t *nsf_load(const char *filename, void *source, int length) +{ + struct nsf_loader_t * loader = 0; + + /* $$$ ben : new loader */ + if (filename) { + nsf_file_loader.fname = (char *)filename; + loader = &nsf_file_loader.loader; + } else { + nsf_mem_loader.data = source; + nsf_mem_loader.len = length; + nsf_mem_loader.fname[0] = 0; + loader = &nsf_mem_loader.loader; + } + return nsf_load_extended(loader); +} + +/* Free an NSF */ +void nsf_free(nsf_t **pnsf) +{ + nsf_t *nsf; + + if (!pnsf) { + return; + } + + nsf = *pnsf; + /* $$$ ben : Don't see why passing a pointer to pointer + * is not to clear it :) */ + *pnsf = 0; + + if (nsf) { + if (nsf->apu) + apu_destroy(nsf->apu); + + nes_shutdown(nsf); + + if (nsf->data) + free(nsf->data); + + if (nsf->song_frames) + free (nsf->song_frames); + + free(nsf); + } +} + +int nsf_setchan(nsf_t *nsf, int chan, boolean enabled) +{ + if (!nsf) + return -1; + + nsf_setcontext(nsf); + return apu_setchan(chan, enabled); +} + +int nsf_playtrack(nsf_t *nsf, int track, int sample_rate, int sample_bits, + boolean stereo) +{ + if (!nsf) { + return -1; + } + + /* 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) + { + /* $$$ ben : from my point of view this is not clean. Function should + * never destroy object it has not created... + */ + /* nsf_free(&nsf); */ + return -1; + } + + 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); + + return nsf->current_song; +} + +int nsf_setfilter(nsf_t *nsf, int filter_type) +{ + if (!nsf) { + return -1; + } + nsf_setcontext(nsf); + return apu_setfilter(filter_type); +} + +/* +** $Log: nsf.c,v $ +** Revision 1.3 2003/05/01 22:34:20 benjihan +** New NSF plugin +** +** Revision 1.2 2003/04/09 14:50:32 ben +** Clean NSF api. +** +** Revision 1.1 2003/04/08 20:53:00 ben +** Adding more files... +** +** 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 +** +*/ |