From acb0917556fc33681c1df5a530cf754193e67705 Mon Sep 17 00:00:00 2001 From: Andree Buschmann Date: Sun, 7 Aug 2011 20:01:04 +0000 Subject: Submit initial patch from FS#12176. Adds support for several new game music formats (AY, GBS, HES, KSS, SGC, VGM and VGZ) and replaces the current NSF and NSFE with a new implementation based on a port of the Game Music Emu library 'GME'. This first submit does not cover the full functionality provided by the author's original patch: Coleco-SGV is not supported, some GME-specific m3u-support has been removed and IRAM is not used yet. Further changes are very likely to follow this submit. Thanks to Mauricio Garrido. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30264 a1c6a512-1295-4272-9138-f99709370657 --- apps/SOURCES | 6 + apps/codecs/SOURCES | 6 + apps/codecs/ay.c | 137 + apps/codecs/codecs.make | 14 + apps/codecs/gbs.c | 108 + apps/codecs/hes.c | 108 + apps/codecs/kss.c | 113 + apps/codecs/libgme/2413tone.h | 20 + apps/codecs/libgme/281btone.h | 20 + apps/codecs/libgme/AYSOURCES | 6 + apps/codecs/libgme/GBSSOURCES | 8 + apps/codecs/libgme/HESSOURCES | 7 + apps/codecs/libgme/KSSSOURCES | 14 + apps/codecs/libgme/NSFSOURCES | 15 + apps/codecs/libgme/SGCSOURCES | 10 + apps/codecs/libgme/VGMSOURCES | 12 + apps/codecs/libgme/ay_apu.c | 413 +++ apps/codecs/libgme/ay_apu.h | 79 + apps/codecs/libgme/ay_cpu.c | 59 + apps/codecs/libgme/ay_emu.c | 783 ++++++ apps/codecs/libgme/ay_emu.h | 172 ++ apps/codecs/libgme/blargg_common.h | 159 ++ apps/codecs/libgme/blargg_config.h | 42 + apps/codecs/libgme/blargg_endian.h | 147 ++ apps/codecs/libgme/blargg_source.h | 71 + apps/codecs/libgme/blip_buffer.c | 285 +++ apps/codecs/libgme/blip_buffer.h | 279 ++ apps/codecs/libgme/emu2413.c | 1958 ++++++++++++++ apps/codecs/libgme/emu2413.h | 164 ++ apps/codecs/libgme/emu8950.c | 1198 +++++++++ apps/codecs/libgme/emu8950.h | 248 ++ apps/codecs/libgme/emuadpcm.c | 298 +++ apps/codecs/libgme/emuadpcm.h | 52 + apps/codecs/libgme/emutables.h | 170 ++ apps/codecs/libgme/emutypes.h | 41 + apps/codecs/libgme/gb_apu.c | 410 +++ apps/codecs/libgme/gb_apu.h | 85 + apps/codecs/libgme/gb_cpu.c | 53 + apps/codecs/libgme/gb_cpu.h | 80 + apps/codecs/libgme/gb_cpu_run.h | 1187 +++++++++ apps/codecs/libgme/gb_oscs.c | 787 ++++++ apps/codecs/libgme/gb_oscs.h | 198 ++ apps/codecs/libgme/gbs_cpu.c | 120 + apps/codecs/libgme/gbs_emu.c | 631 +++++ apps/codecs/libgme/gbs_emu.h | 204 ++ apps/codecs/libgme/gme.h | 18 + apps/codecs/libgme/gme_types.h | 21 + apps/codecs/libgme/hes_apu.c | 315 +++ apps/codecs/libgme/hes_apu.h | 55 + apps/codecs/libgme/hes_apu_adpcm.c | 297 +++ apps/codecs/libgme/hes_apu_adpcm.h | 89 + apps/codecs/libgme/hes_cpu.c | 1321 ++++++++++ apps/codecs/libgme/hes_cpu.h | 95 + apps/codecs/libgme/hes_cpu_io.h | 72 + apps/codecs/libgme/hes_emu.c | 877 +++++++ apps/codecs/libgme/hes_emu.h | 229 ++ apps/codecs/libgme/inflate/bbfuncs.c | 147 ++ apps/codecs/libgme/inflate/bbfuncs.h | 33 + apps/codecs/libgme/inflate/inflate.c | 1159 +++++++++ apps/codecs/libgme/inflate/inflate.h | 30 + apps/codecs/libgme/inflate/mallocer.c | 86 + apps/codecs/libgme/inflate/mallocer.h | 16 + apps/codecs/libgme/inflate/mbreader.c | 16 + apps/codecs/libgme/inflate/mbreader.h | 15 + apps/codecs/libgme/kss_cpu.c | 35 + apps/codecs/libgme/kss_emu.c | 883 +++++++ apps/codecs/libgme/kss_emu.h | 228 ++ apps/codecs/libgme/kss_scc_apu.c | 166 ++ apps/codecs/libgme/kss_scc_apu.h | 51 + apps/codecs/libgme/libay.make | 21 + apps/codecs/libgme/libgbs.make | 21 + apps/codecs/libgme/libhes.make | 21 + apps/codecs/libgme/libkss.make | 21 + apps/codecs/libgme/libnsf.make | 21 + apps/codecs/libgme/libsgc.make | 21 + apps/codecs/libgme/libvgm.make | 21 + apps/codecs/libgme/m3u_playlist.h | 31 + apps/codecs/libgme/msxtypes.h | 36 + apps/codecs/libgme/multi_buffer.c | 226 ++ apps/codecs/libgme/multi_buffer.h | 72 + apps/codecs/libgme/nes_apu.c | 393 +++ apps/codecs/libgme/nes_apu.h | 134 + apps/codecs/libgme/nes_cpu.c | 62 + apps/codecs/libgme/nes_cpu.h | 106 + apps/codecs/libgme/nes_cpu_io.h | 94 + apps/codecs/libgme/nes_cpu_run.h | 1122 +++++++++ apps/codecs/libgme/nes_fds_apu.c | 291 +++ apps/codecs/libgme/nes_fds_apu.h | 116 + apps/codecs/libgme/nes_fme7_apu.c | 135 + apps/codecs/libgme/nes_fme7_apu.h | 90 + apps/codecs/libgme/nes_mmc5_apu.h | 61 + apps/codecs/libgme/nes_namco_apu.c | 133 + apps/codecs/libgme/nes_namco_apu.h | 71 + apps/codecs/libgme/nes_oscs.c | 583 +++++ apps/codecs/libgme/nes_oscs.h | 165 ++ apps/codecs/libgme/nes_vrc6_apu.c | 191 ++ apps/codecs/libgme/nes_vrc6_apu.h | 62 + apps/codecs/libgme/nes_vrc7_apu.c | 89 + apps/codecs/libgme/nes_vrc7_apu.h | 52 + apps/codecs/libgme/nsf_cpu.c | 115 + apps/codecs/libgme/nsf_emu.c | 1105 ++++++++ apps/codecs/libgme/nsf_emu.h | 262 ++ apps/codecs/libgme/nsfe_info.c | 272 ++ apps/codecs/libgme/nsfe_info.h | 30 + apps/codecs/libgme/opl_apu.c | 198 ++ apps/codecs/libgme/opl_apu.h | 62 + apps/codecs/libgme/opltables.h | 242 ++ apps/codecs/libgme/resampler.c | 320 +++ apps/codecs/libgme/resampler.h | 68 + apps/codecs/libgme/rom_data.c | 68 + apps/codecs/libgme/rom_data.h | 83 + apps/codecs/libgme/sgc_cpu.c | 36 + apps/codecs/libgme/sgc_emu.c | 673 +++++ apps/codecs/libgme/sgc_emu.h | 199 ++ apps/codecs/libgme/sms_apu.c | 310 +++ apps/codecs/libgme/sms_apu.h | 63 + apps/codecs/libgme/sms_fm_apu.c | 82 + apps/codecs/libgme/sms_fm_apu.h | 43 + apps/codecs/libgme/vgm_emu.c | 1053 ++++++++ apps/codecs/libgme/vgm_emu.h | 211 ++ apps/codecs/libgme/vrc7tone.h | 20 + apps/codecs/libgme/ym2413_emu.c | 45 + apps/codecs/libgme/ym2413_emu.h | 61 + apps/codecs/libgme/ym2612_emu.c | 1359 ++++++++++ apps/codecs/libgme/ym2612_emu.h | 237 ++ apps/codecs/libgme/ymdeltat.c | 655 +++++ apps/codecs/libgme/ymdeltat.h | 100 + apps/codecs/libgme/ymtables.h | 559 ++++ apps/codecs/libgme/z80_cpu.c | 85 + apps/codecs/libgme/z80_cpu.h | 116 + apps/codecs/libgme/z80_cpu_run.h | 1696 +++++++++++++ apps/codecs/nsf.c | 4485 +-------------------------------- apps/codecs/sgc.c | 123 + apps/codecs/vgm.c | 142 ++ apps/filetypes.c | 7 + apps/metadata.c | 24 + apps/metadata.h | 6 + apps/metadata/ay.c | 148 ++ apps/metadata/gbs.c | 65 + apps/metadata/hes.c | 39 + apps/metadata/kss.c | 53 + apps/metadata/metadata_common.c | 8 + apps/metadata/metadata_common.h | 1 + apps/metadata/metadata_parsers.h | 6 + apps/metadata/nsf.c | 12 +- apps/metadata/sgc.c | 67 + apps/metadata/vgm.c | 195 ++ docs/CREDITS | 1 + manual/appendix/file_formats.tex | 55 +- 149 files changed, 33130 insertions(+), 4428 deletions(-) create mode 100644 apps/codecs/ay.c create mode 100644 apps/codecs/gbs.c create mode 100644 apps/codecs/hes.c create mode 100644 apps/codecs/kss.c create mode 100644 apps/codecs/libgme/2413tone.h create mode 100644 apps/codecs/libgme/281btone.h create mode 100644 apps/codecs/libgme/AYSOURCES create mode 100644 apps/codecs/libgme/GBSSOURCES create mode 100644 apps/codecs/libgme/HESSOURCES create mode 100644 apps/codecs/libgme/KSSSOURCES create mode 100644 apps/codecs/libgme/NSFSOURCES create mode 100644 apps/codecs/libgme/SGCSOURCES create mode 100644 apps/codecs/libgme/VGMSOURCES create mode 100644 apps/codecs/libgme/ay_apu.c create mode 100644 apps/codecs/libgme/ay_apu.h create mode 100644 apps/codecs/libgme/ay_cpu.c create mode 100644 apps/codecs/libgme/ay_emu.c create mode 100644 apps/codecs/libgme/ay_emu.h create mode 100644 apps/codecs/libgme/blargg_common.h create mode 100644 apps/codecs/libgme/blargg_config.h create mode 100644 apps/codecs/libgme/blargg_endian.h create mode 100644 apps/codecs/libgme/blargg_source.h create mode 100644 apps/codecs/libgme/blip_buffer.c create mode 100644 apps/codecs/libgme/blip_buffer.h create mode 100644 apps/codecs/libgme/emu2413.c create mode 100644 apps/codecs/libgme/emu2413.h create mode 100644 apps/codecs/libgme/emu8950.c create mode 100644 apps/codecs/libgme/emu8950.h create mode 100644 apps/codecs/libgme/emuadpcm.c create mode 100644 apps/codecs/libgme/emuadpcm.h create mode 100644 apps/codecs/libgme/emutables.h create mode 100644 apps/codecs/libgme/emutypes.h create mode 100644 apps/codecs/libgme/gb_apu.c create mode 100644 apps/codecs/libgme/gb_apu.h create mode 100644 apps/codecs/libgme/gb_cpu.c create mode 100644 apps/codecs/libgme/gb_cpu.h create mode 100644 apps/codecs/libgme/gb_cpu_run.h create mode 100644 apps/codecs/libgme/gb_oscs.c create mode 100644 apps/codecs/libgme/gb_oscs.h create mode 100644 apps/codecs/libgme/gbs_cpu.c create mode 100644 apps/codecs/libgme/gbs_emu.c create mode 100644 apps/codecs/libgme/gbs_emu.h create mode 100644 apps/codecs/libgme/gme.h create mode 100644 apps/codecs/libgme/gme_types.h create mode 100644 apps/codecs/libgme/hes_apu.c create mode 100644 apps/codecs/libgme/hes_apu.h create mode 100644 apps/codecs/libgme/hes_apu_adpcm.c create mode 100644 apps/codecs/libgme/hes_apu_adpcm.h create mode 100644 apps/codecs/libgme/hes_cpu.c create mode 100644 apps/codecs/libgme/hes_cpu.h create mode 100644 apps/codecs/libgme/hes_cpu_io.h create mode 100644 apps/codecs/libgme/hes_emu.c create mode 100644 apps/codecs/libgme/hes_emu.h create mode 100644 apps/codecs/libgme/inflate/bbfuncs.c create mode 100644 apps/codecs/libgme/inflate/bbfuncs.h create mode 100644 apps/codecs/libgme/inflate/inflate.c create mode 100644 apps/codecs/libgme/inflate/inflate.h create mode 100644 apps/codecs/libgme/inflate/mallocer.c create mode 100644 apps/codecs/libgme/inflate/mallocer.h create mode 100644 apps/codecs/libgme/inflate/mbreader.c create mode 100644 apps/codecs/libgme/inflate/mbreader.h create mode 100644 apps/codecs/libgme/kss_cpu.c create mode 100644 apps/codecs/libgme/kss_emu.c create mode 100644 apps/codecs/libgme/kss_emu.h create mode 100644 apps/codecs/libgme/kss_scc_apu.c create mode 100644 apps/codecs/libgme/kss_scc_apu.h create mode 100644 apps/codecs/libgme/libay.make create mode 100644 apps/codecs/libgme/libgbs.make create mode 100644 apps/codecs/libgme/libhes.make create mode 100644 apps/codecs/libgme/libkss.make create mode 100644 apps/codecs/libgme/libnsf.make create mode 100644 apps/codecs/libgme/libsgc.make create mode 100644 apps/codecs/libgme/libvgm.make create mode 100644 apps/codecs/libgme/m3u_playlist.h create mode 100644 apps/codecs/libgme/msxtypes.h create mode 100644 apps/codecs/libgme/multi_buffer.c create mode 100644 apps/codecs/libgme/multi_buffer.h create mode 100644 apps/codecs/libgme/nes_apu.c create mode 100644 apps/codecs/libgme/nes_apu.h create mode 100644 apps/codecs/libgme/nes_cpu.c create mode 100644 apps/codecs/libgme/nes_cpu.h create mode 100644 apps/codecs/libgme/nes_cpu_io.h create mode 100644 apps/codecs/libgme/nes_cpu_run.h create mode 100644 apps/codecs/libgme/nes_fds_apu.c create mode 100644 apps/codecs/libgme/nes_fds_apu.h create mode 100644 apps/codecs/libgme/nes_fme7_apu.c create mode 100644 apps/codecs/libgme/nes_fme7_apu.h create mode 100644 apps/codecs/libgme/nes_mmc5_apu.h create mode 100644 apps/codecs/libgme/nes_namco_apu.c create mode 100644 apps/codecs/libgme/nes_namco_apu.h create mode 100644 apps/codecs/libgme/nes_oscs.c create mode 100644 apps/codecs/libgme/nes_oscs.h create mode 100644 apps/codecs/libgme/nes_vrc6_apu.c create mode 100644 apps/codecs/libgme/nes_vrc6_apu.h create mode 100644 apps/codecs/libgme/nes_vrc7_apu.c create mode 100644 apps/codecs/libgme/nes_vrc7_apu.h create mode 100644 apps/codecs/libgme/nsf_cpu.c create mode 100644 apps/codecs/libgme/nsf_emu.c create mode 100644 apps/codecs/libgme/nsf_emu.h create mode 100644 apps/codecs/libgme/nsfe_info.c create mode 100644 apps/codecs/libgme/nsfe_info.h create mode 100644 apps/codecs/libgme/opl_apu.c create mode 100644 apps/codecs/libgme/opl_apu.h create mode 100644 apps/codecs/libgme/opltables.h create mode 100644 apps/codecs/libgme/resampler.c create mode 100644 apps/codecs/libgme/resampler.h create mode 100644 apps/codecs/libgme/rom_data.c create mode 100644 apps/codecs/libgme/rom_data.h create mode 100644 apps/codecs/libgme/sgc_cpu.c create mode 100644 apps/codecs/libgme/sgc_emu.c create mode 100644 apps/codecs/libgme/sgc_emu.h create mode 100644 apps/codecs/libgme/sms_apu.c create mode 100644 apps/codecs/libgme/sms_apu.h create mode 100644 apps/codecs/libgme/sms_fm_apu.c create mode 100644 apps/codecs/libgme/sms_fm_apu.h create mode 100644 apps/codecs/libgme/vgm_emu.c create mode 100644 apps/codecs/libgme/vgm_emu.h create mode 100644 apps/codecs/libgme/vrc7tone.h create mode 100644 apps/codecs/libgme/ym2413_emu.c create mode 100644 apps/codecs/libgme/ym2413_emu.h create mode 100644 apps/codecs/libgme/ym2612_emu.c create mode 100644 apps/codecs/libgme/ym2612_emu.h create mode 100644 apps/codecs/libgme/ymdeltat.c create mode 100644 apps/codecs/libgme/ymdeltat.h create mode 100644 apps/codecs/libgme/ymtables.h create mode 100644 apps/codecs/libgme/z80_cpu.c create mode 100644 apps/codecs/libgme/z80_cpu.h create mode 100644 apps/codecs/libgme/z80_cpu_run.h create mode 100644 apps/codecs/sgc.c create mode 100644 apps/codecs/vgm.c create mode 100644 apps/metadata/ay.c create mode 100644 apps/metadata/gbs.c create mode 100644 apps/metadata/hes.c create mode 100644 apps/metadata/kss.c create mode 100644 apps/metadata/sgc.c create mode 100644 apps/metadata/vgm.c diff --git a/apps/SOURCES b/apps/SOURCES index 79642e1f1d..26e53d1bb0 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -227,6 +227,12 @@ metadata/smaf.c metadata/au.c metadata/vox.c metadata/tta.c +metadata/ay.c +metadata/gbs.c +metadata/hes.c +metadata/sgc.c +metadata/vgm.c +metadata/kss.c #endif #ifdef HAVE_TAGCACHE tagcache.c diff --git a/apps/codecs/SOURCES b/apps/codecs/SOURCES index d950ffd46f..508969bd20 100644 --- a/apps/codecs/SOURCES +++ b/apps/codecs/SOURCES @@ -33,6 +33,12 @@ vox.c wav64.c tta.c wmapro.c +ay.c +gbs.c +hes.c +sgc.c +vgm.c +kss.c #ifdef HAVE_RECORDING diff --git a/apps/codecs/ay.c b/apps/codecs/ay.c new file mode 100644 index 0000000000..baccf3d99b --- /dev/null +++ b/apps/codecs/ay.c @@ -0,0 +1,137 @@ + +/* Ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */ + +#include +#include "libgme/ay_emu.h" + +CODEC_HEADER + +/* Maximum number of bytes to process in one iteration */ +#define CHUNK_SIZE (1024*2) + +static int16_t samples[CHUNK_SIZE] IBSS_ATTR; +static struct Ay_Emu ay_emu IDATA_ATTR CACHEALIGN_ATTR; + +/****************** rockbox interface ******************/ + +static void set_codec_track(int t, int multitrack) { + Ay_start_track(&ay_emu, t); + + /* for REPEAT_ONE we disable track limits */ + if (ci->global_settings->repeat_mode != REPEAT_ONE) { + Track_set_fade(&ay_emu, Track_get_length( &ay_emu, t ) - 4000, 4000); + } + if (multitrack) ci->set_elapsed(t*1000); /* t is track no to display */ + else ci->set_elapsed(0); +} + +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* we only render 16 bits */ + ci->configure(DSP_SET_SAMPLE_DEPTH, 16); + + /* 44 Khz, Interleaved stereo */ + ci->configure(DSP_SET_FREQUENCY, 44100); + ci->configure(DSP_SET_STEREO_MODE, STEREO_INTERLEAVED); + + Ay_init(&ay_emu); + Ay_set_sample_rate(&ay_emu, 44100); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ + blargg_err_t err; + uint8_t *buf; + size_t n; + int track, is_multitrack; + intptr_t param; + uint32_t elapsed_time; + + /* reset values */ + track = is_multitrack = 0; + elapsed_time = 0; + + DEBUGF("AY: next_track\n"); + if (codec_init()) { + return CODEC_ERROR; + } + + codec_set_replaygain(ci->id3); + + /* Read the entire file */ + DEBUGF("AY: request file\n"); + ci->seek_buffer(0); + buf = ci->request_buffer(&n, ci->filesize); + if (!buf || n < (size_t)ci->filesize) { + DEBUGF("AY: file load failed\n"); + return CODEC_ERROR; + } + + if ((err = Ay_load_mem(&ay_emu, buf, ci->filesize))) { + DEBUGF("AY: Ay_load failed (%s)\n", err); + return CODEC_ERROR; + } + + /* Update internal track count */ + if (ay_emu.m3u.size > 0) + ay_emu.track_count = ay_emu.m3u.size; + + /* Check if file has multiple tracks */ + if (ay_emu.track_count > 1) { + is_multitrack = 1; + } + +next_track: + set_codec_track(track, is_multitrack); + + /* The main decoder loop */ + while (1) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) + break; + + if (action == CODEC_ACTION_SEEK_TIME) { + if (is_multitrack) { + track = param/1000; + ci->seek_complete(); + if (track >= ay_emu.track_count) break; + goto next_track; + } + + ci->set_elapsed(param); + elapsed_time = param; + Track_seek(&ay_emu, param); + ci->seek_complete(); + + /* Set fade again */ + if (ci->global_settings->repeat_mode != REPEAT_ONE) { + Track_set_fade(&ay_emu, Track_get_length( &ay_emu, track ) - 4000, 4000); + } + } + + /* Generate audio buffer */ + err = Ay_play(&ay_emu, CHUNK_SIZE, samples); + if (err || ay_emu.track_ended) { + track++; + if (track >= ay_emu.track_count) break; + goto next_track; + } + + ci->pcmbuf_insert(samples, NULL, CHUNK_SIZE >> 1); + + /* Set elapsed time for one track files */ + if (!is_multitrack) { + elapsed_time += (CHUNK_SIZE / 2) / 44.1; + ci->set_elapsed(elapsed_time); + } + } + + return CODEC_OK; +} diff --git a/apps/codecs/codecs.make b/apps/codecs/codecs.make index 1a5dd8f36a..19ff60ba3a 100644 --- a/apps/codecs/codecs.make +++ b/apps/codecs/codecs.make @@ -43,6 +43,13 @@ include $(APPSDIR)/codecs/librm/librm.make include $(APPSDIR)/codecs/libatrac/libatrac.make include $(APPSDIR)/codecs/libpcm/libpcm.make include $(APPSDIR)/codecs/libtta/libtta.make +include $(APPSDIR)/codecs/libgme/libay.make +include $(APPSDIR)/codecs/libgme/libgbs.make +include $(APPSDIR)/codecs/libgme/libhes.make +include $(APPSDIR)/codecs/libgme/libnsf.make +include $(APPSDIR)/codecs/libgme/libsgc.make +include $(APPSDIR)/codecs/libgme/libvgm.make +include $(APPSDIR)/codecs/libgme/libkss.make # compile flags for codecs CODECFLAGS = $(CFLAGS) -fstrict-aliasing -I$(APPSDIR)/codecs \ @@ -93,6 +100,13 @@ $(CODECDIR)/au.codec : $(CODECDIR)/libpcm.a $(CODECDIR)/vox.codec : $(CODECDIR)/libpcm.a $(CODECDIR)/wav64.codec : $(CODECDIR)/libpcm.a $(CODECDIR)/tta.codec : $(CODECDIR)/libtta.a +$(CODECDIR)/ay.codec : $(CODECDIR)/libay.a +$(CODECDIR)/gbs.codec : $(CODECDIR)/libgbs.a +$(CODECDIR)/hes.codec : $(CODECDIR)/libhes.a +$(CODECDIR)/nsf.codec : $(CODECDIR)/libnsf.a +$(CODECDIR)/sgc.codec : $(CODECDIR)/libsgc.a +$(CODECDIR)/vgm.codec : $(CODECDIR)/libvgm.a +$(CODECDIR)/kss.codec : $(CODECDIR)/libkss.a $(CODECS): $(CODECLIB) # this must be last in codec dependency list diff --git a/apps/codecs/gbs.c b/apps/codecs/gbs.c new file mode 100644 index 0000000000..bc6d31e6b8 --- /dev/null +++ b/apps/codecs/gbs.c @@ -0,0 +1,108 @@ + +/* Ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */ + +#include +#include "libgme/gbs_emu.h" + +CODEC_HEADER + +/* Maximum number of bytes to process in one iteration */ +#define CHUNK_SIZE (1024*2) + +static int16_t samples[CHUNK_SIZE] IBSS_ATTR; +static struct Gbs_Emu gbs_emu IDATA_ATTR CACHEALIGN_ATTR; + +/****************** rockbox interface ******************/ + +static void set_codec_track(int t) { + Gbs_start_track(&gbs_emu, t); + + /* for REPEAT_ONE we disable track limits */ + if (ci->global_settings->repeat_mode != REPEAT_ONE) { + Track_set_fade(&gbs_emu, Track_get_length( &gbs_emu, t ), 4000); + } + ci->set_elapsed(t*1000); /* t is track no to display */ +} + +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* we only render 16 bits */ + ci->configure(DSP_SET_SAMPLE_DEPTH, 16); + + /* 44 Khz, Interleaved stereo */ + ci->configure(DSP_SET_FREQUENCY, 44100); + ci->configure(DSP_SET_STEREO_MODE, STEREO_INTERLEAVED); + + Gbs_init(&gbs_emu); + Gbs_set_sample_rate(&gbs_emu, 44100); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ + blargg_err_t err; + uint8_t *buf; + size_t n; + intptr_t param; + int track = 0; + + DEBUGF("GBS: next_track\n"); + if (codec_init()) { + return CODEC_ERROR; + } + + codec_set_replaygain(ci->id3); + + /* Read the entire file */ + DEBUGF("GBS: request file\n"); + ci->seek_buffer(0); + buf = ci->request_buffer(&n, ci->filesize); + if (!buf || n < (size_t)ci->filesize) { + DEBUGF("GBS: file load failed\n"); + return CODEC_ERROR; + } + + if ((err = Gbs_load(&gbs_emu, buf, ci->filesize))) { + DEBUGF("GBS: Gbs_load failed (%s)\n", err); + return CODEC_ERROR; + } + + /* Update internal track count */ + if (gbs_emu.m3u.size > 0) + gbs_emu.track_count = gbs_emu.m3u.size; + +next_track: + set_codec_track(track); + + /* The main decoder loop */ + while (1) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) + break; + + if (action == CODEC_ACTION_SEEK_TIME) { + track = param/1000; + ci->seek_complete(); + if (track >= gbs_emu.track_count) break; + goto next_track; + } + + /* Generate audio buffer */ + err = Gbs_play(&gbs_emu, CHUNK_SIZE, samples); + if (err || gbs_emu.track_ended) { + track++; + if (track >= gbs_emu.track_count) break; + goto next_track; + } + + ci->pcmbuf_insert(samples, NULL, CHUNK_SIZE >> 1); + } + + return CODEC_OK; +} diff --git a/apps/codecs/hes.c b/apps/codecs/hes.c new file mode 100644 index 0000000000..598d787594 --- /dev/null +++ b/apps/codecs/hes.c @@ -0,0 +1,108 @@ +/* Ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */ + +#include +#include "codeclib.h" +#include "libgme/hes_emu.h" + +CODEC_HEADER + +/* Maximum number of bytes to process in one iteration */ +#define CHUNK_SIZE (1024*2) + +static int16_t samples[CHUNK_SIZE] IBSS_ATTR; +static struct Hes_Emu hes_emu IDATA_ATTR CACHEALIGN_ATTR; + +/****************** rockbox interface ******************/ + +static void set_codec_track(int t) { + Hes_start_track(&hes_emu, t); + + /* for REPEAT_ONE we disable track limits */ + if (ci->global_settings->repeat_mode != REPEAT_ONE) { + Track_set_fade(&hes_emu, Track_get_length( &hes_emu, t ), 4000); + } + ci->set_elapsed(t*1000); /* t is track no to display */ +} + +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* we only render 16 bits */ + ci->configure(DSP_SET_SAMPLE_DEPTH, 16); + + /* 44 Khz, Interleaved stereo */ + ci->configure(DSP_SET_FREQUENCY, 44100); + ci->configure(DSP_SET_STEREO_MODE, STEREO_INTERLEAVED); + + Hes_init(&hes_emu); + Hes_set_sample_rate(&hes_emu, 44100); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ + blargg_err_t err; + uint8_t *buf; + size_t n; + intptr_t param; + int track = 0; + + DEBUGF("HES: next_track\n"); + if (codec_init()) { + return CODEC_ERROR; + } + + codec_set_replaygain(ci->id3); + + /* Read the entire file */ + DEBUGF("HES: request file\n"); + ci->seek_buffer(0); + buf = ci->request_buffer(&n, ci->filesize); + if (!buf || n < (size_t)ci->filesize) { + DEBUGF("HES: file load failed\n"); + return CODEC_ERROR; + } + + if ((err = Hes_load(&hes_emu, buf, ci->filesize))) { + DEBUGF("HES: Hes_load failed (%s)\n", err); + return CODEC_ERROR; + } + + /* Update internal track count */ + if (hes_emu.m3u.size > 0) + hes_emu.track_count = hes_emu.m3u.size; + +next_track: + set_codec_track(track); + + /* The main decoder loop */ + while ( 1 ) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) + break; + + if (action == CODEC_ACTION_SEEK_TIME) { + track = param/1000; + ci->seek_complete(); + if (track >= hes_emu.track_count) break; + goto next_track; + } + + /* Generate audio buffer */ + err = Hes_play(&hes_emu, CHUNK_SIZE, samples); + if (err || hes_emu.track_ended) { + track++; + if (track >= hes_emu.track_count) break; + goto next_track; + } + + ci->pcmbuf_insert(samples, NULL, CHUNK_SIZE >> 1); + } + + return CODEC_OK; +} diff --git a/apps/codecs/kss.c b/apps/codecs/kss.c new file mode 100644 index 0000000000..9db16521b4 --- /dev/null +++ b/apps/codecs/kss.c @@ -0,0 +1,113 @@ + +/* Ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */ + +#include +#include "libgme/kss_emu.h" + +CODEC_HEADER + +/* Maximum number of bytes to process in one iteration */ +#define CHUNK_SIZE (1024*2) + +static int16_t samples[CHUNK_SIZE] IBSS_ATTR; +static struct Kss_Emu kss_emu IDATA_ATTR CACHEALIGN_ATTR; + +/****************** rockbox interface ******************/ + +static void set_codec_track(int t) { + Kss_start_track(&kss_emu, t); + + /* for REPEAT_ONE we disable track limits */ + if (ci->global_settings->repeat_mode != REPEAT_ONE) { + Track_set_fade(&kss_emu, Track_get_length( &kss_emu, t ), 4000); + } + ci->set_elapsed(t*1000); /* t is track no to display */ +} + +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* we only render 16 bits */ + ci->configure(DSP_SET_SAMPLE_DEPTH, 16); + + /* 44 Khz, Interleaved stereo */ + ci->configure(DSP_SET_FREQUENCY, 44100); + ci->configure(DSP_SET_STEREO_MODE, STEREO_INTERLEAVED); + + Kss_init(&kss_emu); + Kss_set_sample_rate(&kss_emu, 44100); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ + blargg_err_t err; + uint8_t *buf; + size_t n; + int track; + intptr_t param; + uint32_t elapsed_time; + + /* reset values */ + track = 0; + elapsed_time = 0; + + DEBUGF("KSS: next_track\n"); + if (codec_init()) { + return CODEC_ERROR; + } + + codec_set_replaygain(ci->id3); + + /* Read the entire file */ + DEBUGF("KSS: request file\n"); + ci->seek_buffer(0); + buf = ci->request_buffer(&n, ci->filesize); + if (!buf || n < (size_t)ci->filesize) { + DEBUGF("KSS: file load failed\n"); + return CODEC_ERROR; + } + + if ((err = Kss_load_mem(&kss_emu, buf, ci->filesize))) { + DEBUGF("KSS: Kss_load failed (%s)\n", err); + return CODEC_ERROR; + } + + /* Update internal track count */ + if (kss_emu.m3u.size > 0) + kss_emu.track_count = kss_emu.m3u.size; + +next_track: + set_codec_track(track); + + /* The main decoder loop */ + while (1) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) + break; + + if (action == CODEC_ACTION_SEEK_TIME) { + track = param/1000; + ci->seek_complete(); + if (track >= kss_emu.track_count) break; + goto next_track; + } + + /* Generate audio buffer */ + err = Kss_play(&kss_emu, CHUNK_SIZE, samples); + if (err || kss_emu.track_ended) { + track++; + if (track >= kss_emu.track_count) break; + goto next_track; + } + + ci->pcmbuf_insert(samples, NULL, CHUNK_SIZE >> 1); + } + + return CODEC_OK; +} diff --git a/apps/codecs/libgme/2413tone.h b/apps/codecs/libgme/2413tone.h new file mode 100644 index 0000000000..e4366ab245 --- /dev/null +++ b/apps/codecs/libgme/2413tone.h @@ -0,0 +1,20 @@ +/* YM2413 tone by okazaki@angel.ne.jp */ +0x49,0x4c,0x4c,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x61,0x61,0x1e,0x17,0xf0,0x7f,0x00,0x17,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x13,0x41,0x16,0x0e,0xfd,0xf4,0x23,0x23,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x03,0x01,0x9a,0x04,0xf3,0xf3,0x13,0xf3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x11,0x61,0x0e,0x07,0xfa,0x64,0x70,0x17,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x22,0x21,0x1e,0x06,0xf0,0x76,0x00,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x21,0x22,0x16,0x05,0xf0,0x71,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x21,0x61,0x1d,0x07,0x82,0x80,0x17,0x17,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x23,0x21,0x2d,0x16,0x90,0x90,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x21,0x21,0x1b,0x06,0x64,0x65,0x10,0x17,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x21,0x21,0x0b,0x1a,0x85,0xa0,0x70,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x23,0x01,0x83,0x10,0xff,0xb4,0x10,0xf4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x97,0xc1,0x20,0x07,0xff,0xf4,0x22,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x61,0x00,0x0c,0x05,0xc2,0xf6,0x40,0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x01,0x01,0x56,0x03,0x94,0xc2,0x03,0x12,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x21,0x01,0x89,0x03,0xf1,0xe4,0xf0,0x23,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x07,0x21,0x14,0x00,0xee,0xf8,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x01,0x31,0x00,0x00,0xf8,0xf7,0xf8,0xf7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x25,0x11,0x00,0x00,0xf8,0xfa,0xf8,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 diff --git a/apps/codecs/libgme/281btone.h b/apps/codecs/libgme/281btone.h new file mode 100644 index 0000000000..1300523fbe --- /dev/null +++ b/apps/codecs/libgme/281btone.h @@ -0,0 +1,20 @@ +/* YMF281B tone by Chabin */ +0x49,0x4c,0x4c,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x62,0x21,0x1a,0x07,0xf0,0x6f,0x00,0x16,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x10,0x44,0x02,0xf6,0xf4,0x54,0x23,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x03,0x01,0x97,0x04,0xf3,0xf3,0x13,0xf3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x01,0x61,0x0a,0x0f,0xfa,0x64,0x70,0x17,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x22,0x21,0x1e,0x06,0xf0,0x76,0x00,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x61,0x8a,0x0e,0xc0,0x61,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x21,0x61,0x1b,0x07,0x84,0x80,0x17,0x17,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x37,0x32,0xc9,0x01,0x66,0x64,0x40,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x01,0x21,0x06,0x03,0xa5,0x71,0x51,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x06,0x11,0x5e,0x07,0xf3,0xf2,0xf6,0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x20,0x18,0x06,0xf5,0xf3,0x20,0x26,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x97,0x41,0x20,0x07,0xff,0xf4,0x22,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x65,0x61,0x15,0x00,0xf7,0xf3,0x16,0xf4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x01,0x31,0x0e,0x07,0xfa,0xf3,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x48,0x61,0x09,0x07,0xf1,0x94,0xf0,0xf5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x07,0x21,0x14,0x00,0xee,0xf8,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x01,0x31,0x00,0x00,0xf8,0xf7,0xf8,0xf7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x25,0x11,0x00,0x00,0xf8,0xfa,0xf8,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 diff --git a/apps/codecs/libgme/AYSOURCES b/apps/codecs/libgme/AYSOURCES new file mode 100644 index 0000000000..51253fe2f1 --- /dev/null +++ b/apps/codecs/libgme/AYSOURCES @@ -0,0 +1,6 @@ +ay_apu.c +ay_cpu.c +ay_emu.c +blip_buffer.c +multi_buffer.c +z80_cpu.c diff --git a/apps/codecs/libgme/GBSSOURCES b/apps/codecs/libgme/GBSSOURCES new file mode 100644 index 0000000000..5548fd85eb --- /dev/null +++ b/apps/codecs/libgme/GBSSOURCES @@ -0,0 +1,8 @@ +gb_apu.c +gb_cpu.c +gbs_cpu.c +gb_oscs.c +gbs_emu.c +blip_buffer.c +multi_buffer.c +rom_data.c diff --git a/apps/codecs/libgme/HESSOURCES b/apps/codecs/libgme/HESSOURCES new file mode 100644 index 0000000000..58a38f2f5a --- /dev/null +++ b/apps/codecs/libgme/HESSOURCES @@ -0,0 +1,7 @@ +hes_apu.c +hes_apu_adpcm.c +hes_cpu.c +hes_emu.c +blip_buffer.c +multi_buffer.c +rom_data.c diff --git a/apps/codecs/libgme/KSSSOURCES b/apps/codecs/libgme/KSSSOURCES new file mode 100644 index 0000000000..a934bec02a --- /dev/null +++ b/apps/codecs/libgme/KSSSOURCES @@ -0,0 +1,14 @@ +ay_apu.c +kss_cpu.c +kss_emu.c +kss_scc_apu.c +opl_apu.c +sms_apu.c +ymdeltat.c +z80_cpu.c +blip_buffer.c +multi_buffer.c +rom_data.c +emu2413.c +emu8950.c +emuadpcm.c diff --git a/apps/codecs/libgme/NSFSOURCES b/apps/codecs/libgme/NSFSOURCES new file mode 100644 index 0000000000..d96e1d3f32 --- /dev/null +++ b/apps/codecs/libgme/NSFSOURCES @@ -0,0 +1,15 @@ +nes_apu.c +nes_cpu.c +nes_fds_apu.c +nes_fme7_apu.c +nes_namco_apu.c +nes_oscs.c +nes_vrc6_apu.c +nes_vrc7_apu.c +nsf_cpu.c +nsf_emu.c +nsfe_info.c +blip_buffer.c +multi_buffer.c +rom_data.c +emu2413.c diff --git a/apps/codecs/libgme/SGCSOURCES b/apps/codecs/libgme/SGCSOURCES new file mode 100644 index 0000000000..72b06efef9 --- /dev/null +++ b/apps/codecs/libgme/SGCSOURCES @@ -0,0 +1,10 @@ +sgc_cpu.c +sgc_emu.c +sms_apu.c +sms_fm_apu.c +ym2413_emu.c +z80_cpu.c +blip_buffer.c +multi_buffer.c +rom_data.c +emu2413.c diff --git a/apps/codecs/libgme/VGMSOURCES b/apps/codecs/libgme/VGMSOURCES new file mode 100644 index 0000000000..ed32baca0d --- /dev/null +++ b/apps/codecs/libgme/VGMSOURCES @@ -0,0 +1,12 @@ +blip_buffer.c +multi_buffer.c +resampler.c +sms_apu.c +vgm_emu.c +emu2413.c +ym2413_emu.c +ym2612_emu.c +inflate/bbfuncs.c +inflate/inflate.c +inflate/mallocer.c +inflate/mbreader.c diff --git a/apps/codecs/libgme/ay_apu.c b/apps/codecs/libgme/ay_apu.c new file mode 100644 index 0000000000..a2ec299167 --- /dev/null +++ b/apps/codecs/libgme/ay_apu.c @@ -0,0 +1,413 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "ay_apu.h" + +/* Copyright (C) 2006-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// Emulation inaccuracies: +// * Noise isn't run when not in use +// * Changes to envelope and noise periods are delayed until next reload +// * Super-sonic tone should attenuate output to about 60%, not 50% + +// Tones above this frequency are treated as disabled tone at half volume. +// Power of two is more efficient (avoids division). +int const inaudible_freq = 16384; + +int const period_factor = 16; + +static byte const amp_table [16] = +{ +#define ENTRY( n ) (byte) (n * ay_amp_range + 0.5) + // With channels tied together and 1K resistor to ground (as datasheet recommends), + // output nearly matches logarithmic curve as claimed. Approx. 1.5 dB per step. + ENTRY(0.000000),ENTRY(0.007813),ENTRY(0.011049),ENTRY(0.015625), + ENTRY(0.022097),ENTRY(0.031250),ENTRY(0.044194),ENTRY(0.062500), + ENTRY(0.088388),ENTRY(0.125000),ENTRY(0.176777),ENTRY(0.250000), + ENTRY(0.353553),ENTRY(0.500000),ENTRY(0.707107),ENTRY(1.000000), + + /* + // Measured from an AY-3-8910A chip with date code 8611. + + // Direct voltages without any load (very linear) + ENTRY(0.000000),ENTRY(0.046237),ENTRY(0.064516),ENTRY(0.089785), + ENTRY(0.124731),ENTRY(0.173118),ENTRY(0.225806),ENTRY(0.329032), + ENTRY(0.360215),ENTRY(0.494624),ENTRY(0.594624),ENTRY(0.672043), + ENTRY(0.766129),ENTRY(0.841935),ENTRY(0.926882),ENTRY(1.000000), + // With only some load + ENTRY(0.000000),ENTRY(0.011940),ENTRY(0.017413),ENTRY(0.024876), + ENTRY(0.036318),ENTRY(0.054229),ENTRY(0.072637),ENTRY(0.122388), + ENTRY(0.174129),ENTRY(0.239303),ENTRY(0.323881),ENTRY(0.410945), + ENTRY(0.527363),ENTRY(0.651741),ENTRY(0.832338),ENTRY(1.000000), + */ +#undef ENTRY +}; + +static byte const modes [8] = +{ +#define MODE( a0,a1, b0,b1, c0,c1 ) \ + (a0 | a1<<1 | b0<<2 | b1<<3 | c0<<4 | c1<<5) + MODE( 1,0, 1,0, 1,0 ), + MODE( 1,0, 0,0, 0,0 ), + MODE( 1,0, 0,1, 1,0 ), + MODE( 1,0, 1,1, 1,1 ), + MODE( 0,1, 0,1, 0,1 ), + MODE( 0,1, 1,1, 1,1 ), + MODE( 0,1, 1,0, 0,1 ), + MODE( 0,1, 0,0, 0,0 ), +}; + +void set_output( struct Ay_Apu* this, struct Blip_Buffer* b ) +{ + int i; + for ( i = 0; i < ay_osc_count; ++i ) + Ay_apu_set_output( this, i, b ); +} + +void Ay_apu_init( struct Ay_Apu* this ) +{ + Synth_init( &this->synth_ ); + + // build full table of the upper 8 envelope waveforms + int m; + for ( m = 8; m--; ) + { + byte* out = this->env_modes [m]; + int x, y, flags = modes [m]; + for ( x = 3; --x >= 0; ) + { + int amp = flags & 1; + int end = flags >> 1 & 1; + int step = end - amp; + amp *= 15; + for ( y = 16; --y >= 0; ) + { + *out++ = amp_table [amp]; + amp += step; + } + flags >>= 2; + } + } + + set_output( this, NULL ); + Ay_apu_volume( this, 1.0 ); + Ay_apu_reset( this ); +} + +void Ay_apu_reset( struct Ay_Apu* this ) +{ + this->addr_ = 0; + this->last_time = 0; + this->noise_delay = 0; + this->noise_lfsr = 1; + + struct osc_t* osc; + for ( osc = &this->oscs [ay_osc_count]; osc != this->oscs; ) + { + osc--; + osc->period = period_factor; + osc->delay = 0; + osc->last_amp = 0; + osc->phase = 0; + } + + int i; + for ( i = sizeof this->regs; --i >= 0; ) + this->regs [i] = 0; + this->regs [7] = 0xFF; + write_data_( this, 13, 0 ); +} + +int Ay_apu_read( struct Ay_Apu* this ) +{ + static byte const masks [ay_reg_count] = { + 0xFF, 0x0F, 0xFF, 0x0F, 0xFF, 0x0F, 0x1F, 0x3F, + 0x1F, 0x1F, 0x1F, 0xFF, 0xFF, 0x0F, 0x00, 0x00 + }; + return this->regs [this->addr_] & masks [this->addr_]; +} + +void write_data_( struct Ay_Apu* this, int addr, int data ) +{ + assert( (unsigned) addr < ay_reg_count ); + + /* if ( (unsigned) addr >= 14 ) + dprintf( "Wrote to I/O port %02X\n", (int) addr ); */ + + // envelope mode + if ( addr == 13 ) + { + if ( !(data & 8) ) // convert modes 0-7 to proper equivalents + data = (data & 4) ? 15 : 9; + this->env_wave = this->env_modes [data - 7]; + this->env_pos = -48; + this->env_delay = 0; // will get set to envelope period in run_until() + } + this->regs [addr] = data; + + // handle period changes accurately + int i = addr >> 1; + if ( i < ay_osc_count ) + { + blip_time_t period = (this->regs [i * 2 + 1] & 0x0F) * (0x100 * period_factor) + + this->regs [i * 2] * period_factor; + if ( !period ) + period = period_factor; + + // adjust time of next timer expiration based on change in period + struct osc_t* osc = &this->oscs [i]; + if ( (osc->delay += period - osc->period) < 0 ) + osc->delay = 0; + osc->period = period; + } + + // TODO: same as above for envelope timer, and it also has a divide by two after it +} + +int const noise_off = 0x08; +int const tone_off = 0x01; + +void run_until( struct Ay_Apu* this, blip_time_t final_end_time ) +{ + require( final_end_time >= this->last_time ); + + // noise period and initial values + blip_time_t const noise_period_factor = period_factor * 2; // verified + blip_time_t noise_period = (this->regs [6] & 0x1F) * noise_period_factor; + if ( !noise_period ) + noise_period = noise_period_factor; + blip_time_t const old_noise_delay = this->noise_delay; + unsigned const old_noise_lfsr = this->noise_lfsr; + + // envelope period + blip_time_t const env_period_factor = period_factor * 2; // verified + blip_time_t env_period = (this->regs [12] * 0x100 + this->regs [11]) * env_period_factor; + if ( !env_period ) + env_period = env_period_factor; // same as period 1 on my AY chip + if ( !this->env_delay ) + this->env_delay = env_period; + + // run each osc separately + int index; + for ( index = 0; index < ay_osc_count; index++ ) + { + struct osc_t* const osc = &this->oscs [index]; + int osc_mode = this->regs [7] >> index; + + // output + struct Blip_Buffer* const osc_output = osc->output; + if ( !osc_output ) + continue; + Blip_set_modified( osc_output ); + + // period + int half_vol = 0; + blip_time_t inaudible_period = (unsigned) (Blip_clock_rate( osc_output ) + + inaudible_freq) / (unsigned) (inaudible_freq * 2); + if ( osc->period <= inaudible_period && !(osc_mode & tone_off) ) + { + half_vol = 1; // Actually around 60%, but 50% is close enough + osc_mode |= tone_off; + } + + // envelope + blip_time_t start_time = this->last_time; + blip_time_t end_time = final_end_time; + int const vol_mode = this->regs [0x08 + index]; + int volume = amp_table [vol_mode & 0x0F] >> half_vol; + int osc_env_pos = this->env_pos; + if ( vol_mode & 0x10 ) + { + volume = this->env_wave [osc_env_pos] >> half_vol; + // use envelope only if it's a repeating wave or a ramp that hasn't finished + if ( !(this->regs [13] & 1) || osc_env_pos < -32 ) + { + end_time = start_time + this->env_delay; + if ( end_time >= final_end_time ) + end_time = final_end_time; + + //if ( !(regs [12] | regs [11]) ) + // dprintf( "Used envelope period 0\n" ); + } + else if ( !volume ) + { + osc_mode = noise_off | tone_off; + } + } + else if ( !volume ) + { + osc_mode = noise_off | tone_off; + } + + // tone time + blip_time_t const period = osc->period; + blip_time_t time = start_time + osc->delay; + if ( osc_mode & tone_off ) // maintain tone's phase when off + { + int count = (final_end_time - time + period - 1) / period; + time += count * period; + osc->phase ^= count & 1; + } + + // noise time + blip_time_t ntime = final_end_time; + unsigned noise_lfsr = 1; + if ( !(osc_mode & noise_off) ) + { + ntime = start_time + old_noise_delay; + noise_lfsr = old_noise_lfsr; + //if ( (regs [6] & 0x1F) == 0 ) + // dprintf( "Used noise period 0\n" ); + } + + // The following efficiently handles several cases (least demanding first): + // * Tone, noise, and envelope disabled, where channel acts as 4-bit DAC + // * Just tone or just noise, envelope disabled + // * Envelope controlling tone and/or noise + // * Tone and noise disabled, envelope enabled with high frequency + // * Tone and noise together + // * Tone and noise together with envelope + + // this loop only runs one iteration if envelope is disabled. If envelope + // is being used as a waveform (tone and noise disabled), this loop will + // still be reasonably efficient since the bulk of it will be skipped. + while ( 1 ) + { + // current amplitude + int amp = 0; + if ( (osc_mode | osc->phase) & 1 & (osc_mode >> 3 | noise_lfsr) ) + amp = volume; + { + int delta = amp - osc->last_amp; + if ( delta ) + { + osc->last_amp = amp; + Synth_offset( &this->synth_, start_time, delta, osc_output ); + } + } + + // Run wave and noise interleved with each catching up to the other. + // If one or both are disabled, their "current time" will be past end time, + // so there will be no significant performance hit. + if ( ntime < end_time || time < end_time ) + { + // Since amplitude was updated above, delta will always be +/- volume, + // so we can avoid using last_amp every time to calculate the delta. + int delta = amp * 2 - volume; + int delta_non_zero = delta != 0; + int phase = osc->phase | (osc_mode & tone_off); assert( tone_off == 0x01 ); + do + { + // run noise + blip_time_t end = end_time; + if ( end_time > time ) end = time; + if ( phase & delta_non_zero ) + { + while ( ntime <= end ) // must advance *past* time to avoid hang + { + int changed = noise_lfsr + 1; + noise_lfsr = (-(noise_lfsr & 1) & 0x12000) ^ (noise_lfsr >> 1); + if ( changed & 2 ) + { + delta = -delta; + Synth_offset( &this->synth_, ntime, delta, osc_output ); + } + ntime += noise_period; + } + } + else + { + // 20 or more noise periods on average for some music + int remain = end - ntime; + int count = remain / noise_period; + if ( remain >= 0 ) + ntime += noise_period + count * noise_period; + } + + // run tone + end = end_time; + if ( end_time > ntime ) end = ntime; + if ( noise_lfsr & delta_non_zero ) + { + while ( time < end ) + { + delta = -delta; + Synth_offset( &this->synth_, time, delta, osc_output ); + time += period; + + // alternate (less-efficient) implementation + //phase ^= 1; + } + phase = (unsigned) (-delta) >> (CHAR_BIT * sizeof (unsigned) - 1); + check( phase == (delta > 0) ); + } + else + { + // loop usually runs less than once + //SUB_CASE_COUNTER( (time < end) * (end - time + period - 1) / period ); + + while ( time < end ) + { + time += period; + phase ^= 1; + } + } + } + while ( time < end_time || ntime < end_time ); + + osc->last_amp = (delta + volume) >> 1; + if ( !(osc_mode & tone_off) ) + osc->phase = phase; + } + + if ( end_time >= final_end_time ) + break; // breaks first time when envelope is disabled + + // next envelope step + if ( ++osc_env_pos >= 0 ) + osc_env_pos -= 32; + volume = this->env_wave [osc_env_pos] >> half_vol; + + start_time = end_time; + end_time += env_period; + if ( end_time > final_end_time ) + end_time = final_end_time; + } + osc->delay = time - final_end_time; + + if ( !(osc_mode & noise_off) ) + { + this->noise_delay = ntime - final_end_time; + this->noise_lfsr = noise_lfsr; + } + } + + // TODO: optimized saw wave envelope? + + // maintain envelope phase + blip_time_t remain = final_end_time - this->last_time - this->env_delay; + if ( remain >= 0 ) + { + int count = (remain + env_period) / env_period; + this->env_pos += count; + if ( this->env_pos >= 0 ) + this->env_pos = (this->env_pos & 31) - 32; + remain -= count * env_period; + assert( -remain <= env_period ); + } + this->env_delay = -remain; + assert( this->env_delay > 0 ); + assert( this->env_pos < 0 ); + + this->last_time = final_end_time; +} diff --git a/apps/codecs/libgme/ay_apu.h b/apps/codecs/libgme/ay_apu.h new file mode 100644 index 0000000000..ccdd204c46 --- /dev/null +++ b/apps/codecs/libgme/ay_apu.h @@ -0,0 +1,79 @@ +// AY-3-8910 sound chip ulator + +// Game_Music_Emu 0.6-pre +#ifndef AY_APU_H +#define AY_APU_H + +#include "blargg_common.h" +#include "blargg_source.h" +#include "blip_buffer.h" + +// Number of registers +enum { ay_reg_count = 16 }; +enum { ay_osc_count = 3 }; +enum { ay_amp_range = 255 }; + +struct osc_t +{ + blip_time_t period; + blip_time_t delay; + short last_amp; + short phase; + struct Blip_Buffer* output; +}; + +struct Ay_Apu { + struct osc_t oscs [ay_osc_count]; + + blip_time_t last_time; + byte addr_; + byte regs [ay_reg_count]; + + blip_time_t noise_delay; + unsigned noise_lfsr; + + blip_time_t env_delay; + byte const* env_wave; + int env_pos; + byte env_modes [8] [48]; // values already passed through volume table + + struct Blip_Synth synth_; // used by Ay_Core for beeper sound +}; + +void Ay_apu_init( struct Ay_Apu* this ); + +// Writes to address register +static inline void Ay_apu_write_addr( struct Ay_Apu* this, int data ) { this->addr_ = data & 0x0F; } + +// Emulates to time t, then writes to current data register +void run_until( struct Ay_Apu* this, blip_time_t final_end_time ) ICODE_ATTR;; +void write_data_( struct Ay_Apu* this, int addr, int data ) ICODE_ATTR; +static inline void Ay_apu_write_data( struct Ay_Apu* this, blip_time_t t, int data ) { run_until( this, t ); write_data_( this, this->addr_, data ); } + +// Reads from current data register +int Ay_apu_read( struct Ay_Apu* this ); + +// Resets sound chip +void Ay_apu_reset( struct Ay_Apu* this ); + +// Sets overall volume, where 1.0 is normal +static inline void Ay_apu_volume( struct Ay_Apu* this, double v ) { Synth_volume( &this->synth_, 0.7/ay_osc_count/ay_amp_range * v ); } + +static inline void Ay_apu_set_output( struct Ay_Apu* this, int i, struct Blip_Buffer* out ) +{ + assert( (unsigned) i < ay_osc_count ); + this->oscs [i].output = out; +} + +// Emulates to time t, then subtracts t from the current time. +// OK if previous write call had time slightly after t. +static inline void Ay_apu_end_frame( struct Ay_Apu* this, blip_time_t time ) +{ + if ( time > this->last_time ) + run_until( this, time ); + + this->last_time -= time; + assert( this->last_time >= 0 ); +} + +#endif diff --git a/apps/codecs/libgme/ay_cpu.c b/apps/codecs/libgme/ay_cpu.c new file mode 100644 index 0000000000..5fbfe7c1ea --- /dev/null +++ b/apps/codecs/libgme/ay_cpu.c @@ -0,0 +1,59 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "ay_emu.h" + +#include "blargg_endian.h" +//#include "z80_cpu_log.h" + +/* Copyright (C) 2006-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void cpu_out( struct Ay_Emu* this, cpu_time_t time, addr_t addr, int data ) +{ + if ( (addr & 0xFF) == 0xFE ) + { + check( !cpc_mode ); + this->spectrum_mode = !this->cpc_mode; + + // beeper_mask and last_beeper are 0 if (cpc_mode || !beeper_output) + if ( (data &= this->beeper_mask) != this->last_beeper ) + { + this->last_beeper = data; + int delta = -this->beeper_delta; + this->beeper_delta = delta; + struct Blip_Buffer* bb = this->beeper_output; + Blip_set_modified( bb ); + Synth_offset( &this->apu.synth_, time, delta, bb ); + } + } + else + { + cpu_out_( this, time, addr, data ); + } +} + +#define OUT_PORT( addr, data ) cpu_out( this, TIME(), addr, data ) +#define IN_PORT( addr ) 0xFF // cpu in +#define FLAT_MEM mem + +#define CPU_BEGIN \ +bool run_cpu( struct Ay_Emu* this, cpu_time_t end_time ) \ +{\ + struct Z80_Cpu* cpu = &this->cpu; \ + Z80_set_end_time( cpu, end_time ); \ + byte* const mem = this->mem.ram; // cache + + #include "z80_cpu_run.h" + + return warning; +} diff --git a/apps/codecs/libgme/ay_emu.c b/apps/codecs/libgme/ay_emu.c new file mode 100644 index 0000000000..dc775cbf79 --- /dev/null +++ b/apps/codecs/libgme/ay_emu.c @@ -0,0 +1,783 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "ay_emu.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2006-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const stereo = 2; // number of channels for stereo +int const silence_max = 6; // seconds +int const silence_threshold = 0x10; +long const fade_block_size = 512; +int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) + +const char* const gme_wrong_file_type = "Wrong file type for this emulator"; + +// TODO: probably don't need detailed errors as to why file is corrupt + +int const spectrum_clock = 3546900; // 128K Spectrum +int const spectrum_period = 70908; + +//int const spectrum_clock = 3500000; // 48K Spectrum +//int const spectrum_period = 69888; + +int const cpc_clock = 2000000; + +void clear_track_vars( struct Ay_Emu *this ) +{ + this->current_track = -1; + this->out_time = 0; + this->emu_time = 0; + this->emu_track_ended_ = true; + this->track_ended = true; + this->fade_start = INT_MAX / 2 + 1; + this->fade_step = 1; + this->silence_time = 0; + this->silence_count = 0; + this->buf_remain = 0; + /* warning(); // clear warning */ +} + +void Ay_init( struct Ay_Emu *this ) +{ + this->sample_rate = 0; + this->mute_mask_ = 0; + this->tempo = 1.0; + this->gain = 1.0; + this->track_count = 0; + + // defaults + this->max_initial_silence = 2; + this->ignore_silence = false; + + this->voice_count = 0; + clear_track_vars( this ); + this->beeper_output = NULL; + disable_beeper( this ); + + Ay_apu_init( &this->apu ); + Z80_init( &this->cpu ); + + this->silence_lookahead = 6 ; +} + +// Track info + +// Given pointer to 2-byte offset of data, returns pointer to data, or NULL if +// offset is 0 or there is less than min_size bytes of data available. +static byte const* get_data( struct file_t const* file, byte const ptr [], int min_size ) +{ + int offset = (int16_t) get_be16( ptr ); + int pos = ptr - (byte const*) file->header; + int size = file->end - (byte const*) file->header; + assert( (unsigned) pos <= (unsigned) size - 2 ); + int limit = size - min_size; + if ( limit < 0 || !offset || (unsigned) (pos + offset) > (unsigned) limit ) + return NULL; + return ptr + offset; +} + +static blargg_err_t parse_header( byte const in [], int size, struct file_t* out ) +{ + if ( size < header_size ) + return gme_wrong_file_type; + + out->header = (struct header_t const*) in; + out->end = in + size; + struct header_t const* h = (struct header_t const*) in; + if ( memcmp( h->tag, "ZXAYEMUL", 8 ) ) + return gme_wrong_file_type; + + out->tracks = get_data( out, h->track_info, (h->max_track + 1) * 4 ); + if ( !out->tracks ) + return "missing track data"; + + return 0; +} + +long Track_get_length( struct Ay_Emu* this, int n ) +{ + long length = 0; + + byte const* track_info = get_data( &this->file, this->file.tracks + n * 4 + 2, 6 ); + if ( track_info ) + length = get_be16( track_info + 4 ) * (1000 / 50); // frames to msec + + if ( (this->m3u.size > 0) && (n < this->m3u.size) ) { + struct entry_t* entry = &this->m3u.entries [n]; + length = entry->length; + } + + if ( length <= 0 ) + length = 120 * 1000; /* 2 minutes */ + + return length; +} + +// Setup + +void change_clock_rate( struct Ay_Emu *this, long rate ) +{ + this->clock_rate_ = rate; + Buffer_clock_rate( &this->stereo_buf, rate ); +} + +blargg_err_t Ay_load_mem( struct Ay_Emu *this, byte const in [], int size ) +{ + assert( offsetof (struct header_t,track_info [2]) == header_size ); + + RETURN_ERR( parse_header( in, size, &this->file ) ); + + /* if ( file.header->vers > 2 ) + warning( "Unknown file version" ); */ + + this->voice_count = ay_osc_count + 1; // +1 for beeper + Ay_apu_volume( &this->apu, this->gain ); + + // Setup buffer + change_clock_rate( this, spectrum_clock ); + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); + + Sound_set_tempo( this, this->tempo ); + + // Remute voices + Sound_mute_voices( this, this->mute_mask_ ); + + this->track_count = this->file.header->max_track + 1; + this->m3u.size = 0; + return 0; +} + +void set_beeper_output( struct Ay_Emu *this, struct Blip_Buffer* b ) +{ + this->beeper_output = b; + if ( b && !this->cpc_mode ) + this->beeper_mask = 0x10; + else + disable_beeper( this ); +} + +void set_voice( struct Ay_Emu *this, int i, struct Blip_Buffer* center ) +{ + if ( i >= ay_osc_count ) + set_beeper_output( this, center ); + else + Ay_apu_set_output( &this->apu, i, center ); +} + +blargg_err_t run_clocks( struct Ay_Emu *this, blip_time_t* duration, int msec ) +{ +#if defined(ROCKBOX) + (void) msec; +#endif + + cpu_time_t *end = duration; + struct Z80_Cpu* cpu = &this->cpu; + Z80_set_time( cpu, 0 ); + + // Since detection of CPC mode will halve clock rate during the frame + // and thus generate up to twice as much sound, we must generate half + // as much until mode is known. + if ( !(this->spectrum_mode | this->cpc_mode) ) + *end /= 2; + + while ( Z80_time( cpu ) < *end ) + { + run_cpu( this, min( *end, this->next_play ) ); + + if ( Z80_time( cpu ) >= this->next_play ) + { + // next frame + this->next_play += this->play_period; + + if ( cpu->r.iff1 ) + { + // interrupt enabled + + if ( this->mem.ram [cpu->r.pc] == 0x76 ) + cpu->r.pc++; // advance past HALT instruction + + cpu->r.iff1 = 0; + cpu->r.iff2 = 0; + + this->mem.ram [--cpu->r.sp] = (byte) (cpu->r.pc >> 8); + this->mem.ram [--cpu->r.sp] = (byte) (cpu->r.pc); + + // fixed interrupt + cpu->r.pc = 0x38; + Z80_adjust_time( cpu, 12 ); + + if ( cpu->r.im == 2 ) + { + // vectored interrupt + addr_t addr = cpu->r.i * 0x100 + 0xFF; + cpu->r.pc = this->mem.ram [(addr + 1) & 0xFFFF] * 0x100 + this->mem.ram [addr]; + Z80_adjust_time( cpu, 6 ); + } + } + } + } + + // End time frame + *end = Z80_time( cpu ); + this->next_play -= *end; + check( this->next_play >= 0 ); + Z80_adjust_time( cpu, -*end ); + Ay_apu_end_frame( &this->apu, *end ); + return 0; +} + +// Emulation + +void cpu_out_( struct Ay_Emu *this, cpu_time_t time, addr_t addr, int data ) +{ + // Spectrum + if ( !this->cpc_mode ) + { + switch ( addr & 0xFEFF ) + { + case 0xFEFD: + this->spectrum_mode = true; + Ay_apu_write_addr( &this->apu, data ); + return; + + case 0xBEFD: + this->spectrum_mode = true; + Ay_apu_write_data( &this->apu, time, data ); + return; + } + } + + // CPC + if ( !this->spectrum_mode ) + { + switch ( addr >> 8 ) + { + case 0xF6: + switch ( data & 0xC0 ) + { + case 0xC0: + Ay_apu_write_addr( &this->apu, this->cpc_latch ); + goto enable_cpc; + + case 0x80: + Ay_apu_write_data( &this->apu, time, this->cpc_latch ); + goto enable_cpc; + } + break; + + case 0xF4: + this->cpc_latch = data; + goto enable_cpc; + } + } + + /* dprintf( "Unmapped OUT: $%04X <- $%02X\n", addr, data ); */ + return; + +enable_cpc: + if ( !this->cpc_mode ) + { + this->cpc_mode = true; + disable_beeper( this ); + + change_clock_rate( this, cpc_clock ); + Sound_set_tempo( this, this->tempo ); + } +} + +blargg_err_t Ay_set_sample_rate( struct Ay_Emu *this, long rate ) +{ + require( !this->sample_rate ); // sample rate can't be changed once set + Buffer_init( &this->stereo_buf ); + RETURN_ERR( Buffer_set_sample_rate( &this->stereo_buf, rate, 1000 / 20 ) ); + + // Set buffer bass + Buffer_bass_freq( &this->stereo_buf, 160 ); + + this->sample_rate = rate; + return 0; +} + +void Sound_mute_voice( struct Ay_Emu *this, int index, bool mute ) +{ + require( (unsigned) index < (unsigned) this->voice_count ); + int bit = 1 << index; + int mask = this->mute_mask_ | bit; + if ( !mute ) + mask ^= bit; + Sound_mute_voices( this, mask ); +} + +void Sound_mute_voices( struct Ay_Emu *this, int mask ) +{ + require( this->sample_rate ); // sample rate must be set first + this->mute_mask_ = mask; + + int i; + for ( i = this->voice_count; i--; ) + { + if ( mask & (1 << i) ) + { + set_voice( this, i, 0 ); + } + else + { + struct channel_t ch = Buffer_channel( &this->stereo_buf ); + assert( (ch.center && ch.left && ch.right) || + (!ch.center && !ch.left && !ch.right) ); // all or nothing + set_voice( this, i, ch.center ); + } + } +} + +void Sound_set_tempo( struct Ay_Emu *this, double t ) +{ + require( this->sample_rate ); // sample rate must be set first + double const min = 0.02; + double const max = 4.00; + if ( t < min ) t = min; + if ( t > max ) t = max; + this->tempo = t; + + int p = spectrum_period; + if ( this->clock_rate_ != spectrum_clock ) + p = this->clock_rate_ / 50; + + this->play_period = (blip_time_t) (p / t); +} + +void fill_buf( struct Ay_Emu *this ) ICODE_ATTR;; +blargg_err_t Ay_start_track( struct Ay_Emu *this, int track ) +{ + clear_track_vars( this ); + + // Remap track if playlist available + if ( this->m3u.size > 0 ) { + struct entry_t* e = &this->m3u.entries[track]; + track = e->track; + } + + this->current_track = track; + Buffer_clear( &this->stereo_buf ); + + byte* const mem = this->mem.ram; + + memset( mem + 0x0000, 0xC9, 0x100 ); // fill RST vectors with RET + memset( mem + 0x0100, 0xFF, 0x4000 - 0x100 ); + memset( mem + ram_addr, 0x00, mem_size - ram_addr ); + + // locate data blocks + byte const* const data = get_data( &this->file, this->file.tracks + track * 4 + 2, 14 ); + if ( !data ) + return "file data missing"; + + byte const* const more_data = get_data( &this->file, data + 10, 6 ); + if ( !more_data ) + return "file data missing"; + + byte const* blocks = get_data( &this->file, data + 12, 8 ); + if ( !blocks ) + return "file data missing"; + + // initial addresses + unsigned addr = get_be16( blocks ); + if ( !addr ) + return "file data missing"; + + unsigned init = get_be16( more_data + 2 ); + if ( !init ) + init = addr; + + // copy blocks into memory + do + { + blocks += 2; + unsigned len = get_be16( blocks ); blocks += 2; + if ( addr + len > mem_size ) + { + /* warning( "Bad data block size" ); */ + len = mem_size - addr; + } + check( len ); + byte const* in = get_data( &this->file, blocks, 0 ); blocks += 2; + if ( len > (unsigned) (this->file.end - in) ) + { + /* warning( "File data missing" ); */ + len = this->file.end - in; + } + + memcpy( mem + addr, in, len ); + + if ( this->file.end - blocks < 8 ) + { + /* warning( "File data missing" ); */ + break; + } + } + while ( (addr = get_be16( blocks )) != 0 ); + + // copy and configure driver + static byte const passive [] = { + 0xF3, // DI + 0xCD, 0, 0, // CALL init + 0xED, 0x5E, // LOOP: IM 2 + 0xFB, // EI + 0x76, // HALT + 0x18, 0xFA // JR LOOP + }; + static byte const active [] = { + 0xF3, // DI + 0xCD, 0, 0, // CALL init + 0xED, 0x56, // LOOP: IM 1 + 0xFB, // EI + 0x76, // HALT + 0xCD, 0, 0, // CALL play + 0x18, 0xF7 // JR LOOP + }; + memcpy( mem, passive, sizeof passive ); + int const play_addr = get_be16( more_data + 4 ); + if ( play_addr ) + { + memcpy( mem, active, sizeof active ); + mem [ 9] = play_addr; + mem [10] = play_addr >> 8; + } + mem [2] = init; + mem [3] = init >> 8; + + mem [0x38] = 0xFB; // Put EI at interrupt vector (followed by RET) + + // start at spectrum speed + change_clock_rate( this, spectrum_clock ); + Sound_set_tempo( this, this->tempo ); + + struct registers_t r; + memset( &r, 0, sizeof(struct registers_t) ); + + r.sp = get_be16( more_data ); + r.b.a = r.b.b = r.b.d = r.b.h = data [8]; + r.b.flags = r.b.c = r.b.e = r.b.l = data [9]; + r.alt.w = r.w; + r.ix = r.iy = r.w.hl; + + memset( this->mem.padding1, 0xFF, sizeof this->mem.padding1 ); + + int const mirrored = 0x80; // this much is mirrored after end of memory + memset( this->mem.ram + mem_size + mirrored, 0xFF, sizeof this->mem.ram - mem_size - mirrored ); + memcpy( this->mem.ram + mem_size, this->mem.ram, mirrored ); // some code wraps around (ugh) + + Z80_reset( &this->cpu, this->mem.padding1, this->mem.padding1 ); + Z80_map_mem( &this->cpu, 0, mem_size, this->mem.ram, this->mem.ram ); + this->cpu.r = r; + + this->beeper_delta = (int) (ay_amp_range * 0.8); + this->last_beeper = 0; + this->next_play = this->play_period; + this->spectrum_mode = false; + this->cpc_mode = false; + this->cpc_latch = 0; + set_beeper_output( this, this->beeper_output ); + Ay_apu_reset( &this->apu ); + + // a few tunes rely on channels having tone enabled at the beginning + Ay_apu_write_addr( &this->apu, 7 ); + Ay_apu_write_data( &this->apu, 0, 0x38 ); + + this->emu_track_ended_ = false; + this->track_ended = false; + + if ( !this->ignore_silence ) + { + // play until non-silence or end of track + long end; + for ( end = this->max_initial_silence * stereo * this->sample_rate; this->emu_time < end; ) + { + fill_buf( this ); + if ( this->buf_remain | (int) this->emu_track_ended_ ) + break; + } + + this->emu_time = this->buf_remain; + this->out_time = 0; + this->silence_time = 0; + this->silence_count = 0; + } + /* return track_ended() ? warning() : 0; */ + return 0; +} + +// Tell/Seek + +blargg_long msec_to_samples( blargg_long msec, long sample_rate ) +{ + blargg_long sec = msec / 1000; + msec -= sec * 1000; + return (sec * sample_rate + msec * sample_rate / 1000) * stereo; +} + +long Track_tell( struct Ay_Emu *this ) +{ + blargg_long rate = this->sample_rate * stereo; + blargg_long sec = this->out_time / rate; + return sec * 1000 + (this->out_time - sec * rate) * 1000 / rate; +} + +blargg_err_t Track_seek( struct Ay_Emu *this, long msec ) +{ + blargg_long time = msec_to_samples( msec, this->sample_rate ); + if ( time < this->out_time ) + RETURN_ERR( Ay_start_track( this, this->current_track ) ); + return Track_skip( this, time - this->out_time ); +} + +blargg_err_t play_( struct Ay_Emu *this, long count, sample_t* out ) ICODE_ATTR; +blargg_err_t skip_( struct Ay_Emu *this, long count ) +{ + // for long skip, mute sound + const long threshold = 30000; + if ( count > threshold ) + { + int saved_mute = this->mute_mask_; + Sound_mute_voices( this, ~0 ); + + while ( count > threshold / 2 && !this->emu_track_ended_ ) + { + RETURN_ERR( play_( this, buf_size, this->buf ) ); + count -= buf_size; + } + + Sound_mute_voices( this, saved_mute ); + } + + while ( count && !this->emu_track_ended_ ) + { + long n = buf_size; + if ( n > count ) + n = count; + count -= n; + RETURN_ERR( play_( this, n, this->buf ) ); + } + return 0; +} + +blargg_err_t Track_skip( struct Ay_Emu *this, long count ) +{ + require( this->current_track >= 0 ); // start_track() must have been called already + this->out_time += count; + + // remove from silence and buf first + { + long n = min( count, this->silence_count ); + this->silence_count -= n; + count -= n; + + n = min( count, this->buf_remain ); + this->buf_remain -= n; + count -= n; + } + + if ( count && !this->emu_track_ended_ ) + { + this->emu_time += count; + + // End track if error + if ( skip_( this, count ) ) + this->emu_track_ended_ = true; + } + + if ( !(this->silence_count | this->buf_remain) ) // caught up to emulator, so update track ended + this->track_ended |= this->emu_track_ended_; + + return 0; +} + +// Fading + +void Track_set_fade( struct Ay_Emu *this, long start_msec, long length_msec ) +{ + this->fade_step = this->sample_rate * length_msec / (fade_block_size * fade_shift * 1000 / stereo); + this->fade_start = msec_to_samples( start_msec, this->sample_rate ); +} + +// unit / pow( 2.0, (double) x / step ) +static int int_log( blargg_long x, int step, int unit ) +{ + int shift = x / step; + int fraction = (x - shift * step) * unit / step; + return ((unit - fraction) + (fraction >> 1)) >> shift; +} + +void handle_fade( struct Ay_Emu *this, long out_count, sample_t* out ) +{ + int i; + for ( i = 0; i < out_count; i += fade_block_size ) + { + int const shift = 14; + int const unit = 1 << shift; + int gain = int_log( (this->out_time + i - this->fade_start) / fade_block_size, + this->fade_step, unit ); + if ( gain < (unit >> fade_shift) ) + this->track_ended = this->emu_track_ended_ = true; + + sample_t* io = &out [i]; + int count; + for ( count = min( fade_block_size, out_count - i ); count; --count ) + { + *io = (sample_t) ((*io * gain) >> shift); + ++io; + } + } +} + +// Silence detection + +void emu_play( struct Ay_Emu *this, long count, sample_t* out ) +{ + check( current_track_ >= 0 ); + this->emu_time += count; + if ( this->current_track >= 0 && !this->emu_track_ended_ ) { + if ( play_( this, count, out ) ) + this->emu_track_ended_ = true; + } + else + memset( out, 0, count * sizeof *out ); +} + +// number of consecutive silent samples at end +static long count_silence( sample_t* begin, long size ) +{ + sample_t first = *begin; + *begin = silence_threshold; // sentinel + sample_t* p = begin + size; + while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { } + *begin = first; + return size - (p - begin); +} + +// fill internal buffer and check it for silence +void fill_buf( struct Ay_Emu *this ) +{ + assert( !this->buf_remain ); + if ( !this->emu_track_ended_ ) + { + emu_play( this, buf_size, this->buf ); + long silence = count_silence( this->buf, buf_size ); + if ( silence < buf_size ) + { + this->silence_time = this->emu_time - silence; + this->buf_remain = buf_size; + return; + } + } + this->silence_count += buf_size; +} + +blargg_err_t Ay_play( struct Ay_Emu *this, long out_count, sample_t* out ) +{ + if ( this->track_ended ) + { + memset( out, 0, out_count * sizeof *out ); + } + else + { + require( this->current_track >= 0 ); + require( out_count % stereo == 0 ); + + assert( this->emu_time >= this->out_time ); + + // prints nifty graph of how far ahead we are when searching for silence + //debug_printf( "%*s \n", int ((emu_time - out_time) * 7 / sample_rate()), "*" ); + + long pos = 0; + if ( this->silence_count ) + { + // during a run of silence, run emulator at >=2x speed so it gets ahead + long ahead_time = this->silence_lookahead * (this->out_time + out_count - this->silence_time) + this->silence_time; + while ( this->emu_time < ahead_time && !(this->buf_remain | this->emu_track_ended_) ) + fill_buf( this ); + + // fill with silence + pos = min( this->silence_count, out_count ); + memset( out, 0, pos * sizeof *out ); + this->silence_count -= pos; + + if ( this->emu_time - this->silence_time > silence_max * stereo * this->sample_rate ) + { + this->track_ended = this->emu_track_ended_ = true; + this->silence_count = 0; + this->buf_remain = 0; + } + } + + if ( this->buf_remain ) + { + // empty silence buf + long n = min( this->buf_remain, out_count - pos ); + memcpy( &out [pos], this->buf + (buf_size - this->buf_remain), n * sizeof *out ); + this->buf_remain -= n; + pos += n; + } + + // generate remaining samples normally + long remain = out_count - pos; + if ( remain ) + { + emu_play( this, remain, out + pos ); + this->track_ended |= this->emu_track_ended_; + + if ( !this->ignore_silence || this->out_time > this->fade_start ) + { + // check end for a new run of silence + long silence = count_silence( out + pos, remain ); + if ( silence < remain ) + this->silence_time = this->emu_time - silence; + + if ( this->emu_time - this->silence_time >= buf_size ) + fill_buf( this ); // cause silence detection on next play() + } + } + + if ( this->out_time > this->fade_start ) + handle_fade( this, out_count, out ); + } + this->out_time += out_count; + return 0; +} + +blargg_err_t play_( struct Ay_Emu *this, long count, sample_t* out ) +{ + long remain = count; + while ( remain ) + { + remain -= Buffer_read_samples( &this->stereo_buf, &out [count - remain], remain ); + if ( remain ) + { + if ( this->buf_changed_count != Buffer_channels_changed_count( &this->stereo_buf ) ) + { + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); + + // Remute voices + Sound_mute_voices( this, this->mute_mask_ ); + } + int msec = Buffer_length( &this->stereo_buf ); + blip_time_t clocks_emulated = (blargg_long) msec * this->clock_rate_ / 1000 - 100; + RETURN_ERR( run_clocks( this, &clocks_emulated, msec ) ); + assert( clocks_emulated ); + Buffer_end_frame( &this->stereo_buf, clocks_emulated ); + } + } + return 0; +} diff --git a/apps/codecs/libgme/ay_emu.h b/apps/codecs/libgme/ay_emu.h new file mode 100644 index 0000000000..91252166e4 --- /dev/null +++ b/apps/codecs/libgme/ay_emu.h @@ -0,0 +1,172 @@ +// Sinclair Spectrum AY music file emulator + +// Game_Music_Emu 0.6-pre +#ifndef AY_EMU_H +#define AY_EMU_H + +#include "blargg_source.h" + +#include "multi_buffer.h" +#include "z80_cpu.h" +#include "ay_apu.h" +#include "m3u_playlist.h" + +typedef short sample_t; + +// 64K memory to load code and data into before starting track. Caller +// must parse the AY file. +enum { mem_size = 0x10000 }; +enum { ram_addr = 0x4000 }; // where official RAM starts +enum { buf_size = 2048 }; + +// AY file header +enum { header_size = 0x14 }; +struct header_t +{ + byte tag [8]; + byte vers; + byte player; + byte unused [2]; + byte author [2]; + byte comment [2]; + byte max_track; + byte first_track; + byte track_info [2]; +}; + +struct file_t { + struct header_t const* header; + byte const* tracks; + byte const* end; // end of file data +}; + +struct mem_t { + uint8_t padding1 [0x100]; + uint8_t ram [mem_size + 0x100]; +}; + +struct Ay_Emu { + struct file_t file; + + struct Blip_Buffer* beeper_output; + int beeper_delta; + int last_beeper; + int beeper_mask; + + addr_t play_addr; + cpu_time_t play_period; + cpu_time_t next_play; + + int cpc_latch; + bool spectrum_mode; + bool cpc_mode; + + // general + int max_initial_silence; + int voice_count; + int mute_mask_; + double tempo; + double gain; + + long sample_rate; + + // track-specific + int current_track; + int track_count; + blargg_long out_time; // number of samples played since start of track + blargg_long emu_time; // number of samples emulator has generated since start of track + volatile bool track_ended; + bool emu_track_ended_; // emulator has reached end of track + + // fading + blargg_long fade_start; + int fade_step; + + // silence detection + bool ignore_silence; + int silence_lookahead; // speed to run emulator when looking ahead for silence + long silence_time; // number of samples where most recent silence began + long silence_count; // number of samples of silence to play before using buf + long buf_remain; // number of samples left in silence buffer + + long clock_rate_; + unsigned buf_changed_count; + + // M3u Playlist + struct M3u_Playlist m3u; + + // large items + struct Ay_Apu apu; + sample_t buf [buf_size]; + struct Stereo_Buffer stereo_buf; // NULL if using custom buffer + struct Z80_Cpu cpu; + struct mem_t mem; +}; + +// Basic functionality (see Gme_File.h for file loading/track info functions) +void Ay_init( struct Ay_Emu* this ); + +blargg_err_t Ay_load_mem( struct Ay_Emu* this, byte const in [], int size ); + +// Set output sample rate. Must be called only once before loading file. +blargg_err_t Ay_set_sample_rate( struct Ay_Emu* this, long sample_rate ); + +// Start a track, where 0 is the first track. Also clears warning string. +blargg_err_t Ay_start_track( struct Ay_Emu* this, int track ); + +// Generate 'count' samples info 'buf'. Output is in stereo. Any emulation +// errors set warning string, and major errors also end track. +blargg_err_t Ay_play( struct Ay_Emu* this, long count, sample_t* buf ); + + +// Track status/control + +// Number of milliseconds (1000 msec = 1 second) played since beginning of track +long Track_tell( struct Ay_Emu* this ); + +// Seek to new time in track. Seeking backwards or far forward can take a while. +blargg_err_t Track_seek( struct Ay_Emu* this, long msec ); + +// Skip n samples +blargg_err_t Track_skip( struct Ay_Emu* this, long n ); + +// Set start time and length of track fade out. Once fade ends track_ended() returns +// true. Fade time can be changed while track is playing. +void Track_set_fade( struct Ay_Emu* this, long start_msec, long length_msec ); + +// Get track length in milliseconds +long Track_get_length( struct Ay_Emu* this, int n ); + +// Sound customization + +// Adjust song tempo, where 1.0 = normal, 0.5 = half speed, 2.0 = double speed. +// Track length as returned by track_info() assumes a tempo of 1.0. +void Sound_set_tempo( struct Ay_Emu* this, double t ); + +// Mute/unmute voice i, where voice 0 is first voice +void Sound_mute_voice( struct Ay_Emu* this, int index, bool mute ); + +// Set muting state of all voices at once using a bit mask, where -1 mutes them all, +// 0 unmutes them all, 0x01 mutes just the first voice, etc. +void Sound_mute_voices( struct Ay_Emu* this, int mask ); + +// Change overall output amplitude, where 1.0 results in minimal clamping. +// Must be called before set_sample_rate(). +static inline void Sound_set_gain( struct Ay_Emu* this, double g ) +{ + assert( !this->sample_rate ); // you must set gain before setting sample rate + this->gain = g; +} + +// Emulation (You shouldn't touch these) +void cpu_out( struct Ay_Emu* this, cpu_time_t, addr_t, int data ); +void cpu_out_( struct Ay_Emu* this, cpu_time_t, addr_t, int data ); +bool run_cpu( struct Ay_Emu* this, cpu_time_t end ); + +static inline void disable_beeper( struct Ay_Emu *this ) +{ + this->beeper_mask = 0; + this->last_beeper = 0; +} + +#endif diff --git a/apps/codecs/libgme/blargg_common.h b/apps/codecs/libgme/blargg_common.h new file mode 100644 index 0000000000..be34379441 --- /dev/null +++ b/apps/codecs/libgme/blargg_common.h @@ -0,0 +1,159 @@ +// Sets up common environment for Shay Green's libraries. +// To change configuration options, modify blargg_config.h, not this file. + +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +#include +#include +#include +#include +#include + +#undef BLARGG_COMMON_H +// allow blargg_config.h to #include blargg_common.h +#include "blargg_config.h" +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +#if defined(ROCKBOX) +#include "codeclib.h" +#endif + +#if 1 /* IRAM configuration is not yet active for all libGME codecs. */ + #undef ICODE_ATTR + #define ICODE_ATTR + + #undef IDATA_ATTR + #define IDATA_ATTR + + #undef ICONST_ATTR + #define ICONST_ATTR + + #undef IBSS_ATTR + #define IBSS_ATTR +#endif + +// BLARGG_RESTRICT: equivalent to C99's restrict, where supported +#if __GNUC__ >= 3 || _MSC_VER >= 1100 + #define BLARGG_RESTRICT __restrict +#else + #define BLARGG_RESTRICT +#endif + +// STATIC_CAST(T,expr): Used in place of static_cast (expr) +#ifndef STATIC_CAST + #define STATIC_CAST(T,expr) ((T) (expr)) +#endif + +// blargg_err_t (0 on success, otherwise error string) +#ifndef blargg_err_t + typedef const char* blargg_err_t; +#endif + +#define BLARGG_4CHAR( a, b, c, d ) \ + ((a&0xFF)*0x1000000L + (b&0xFF)*0x10000L + (c&0xFF)*0x100L + (d&0xFF)) + +// BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0. +#ifndef BOOST_STATIC_ASSERT + #ifdef _MSC_VER + // MSVC6 (_MSC_VER < 1300) fails for use of __LINE__ when /Zl is specified + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / (int) !!(expr) - 1] ) + #else + // Some other compilers fail when declaring same function multiple times in class, + // so differentiate them by line + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] ) + #endif +#endif + +// BLARGG_COMPILER_HAS_BOOL: If 0, provides bool support for old compiler. If 1, +// compiler is assumed to support bool. If undefined, availability is determined. +#ifndef BLARGG_COMPILER_HAS_BOOL + #if defined (__MWERKS__) + #if !__option(bool) + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (_MSC_VER) + #if _MSC_VER < 1100 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (__GNUC__) + // supports bool + #elif __cplusplus < 199711 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif +#endif +#if defined (BLARGG_COMPILER_HAS_BOOL) && !BLARGG_COMPILER_HAS_BOOL + // If you get errors here, modify your blargg_config.h file + typedef int bool; + static bool true = 1; + static bool false = 0; +#endif + +// blargg_long/blargg_ulong = at least 32 bits, int if it's big enough +#include + +#if INT_MAX >= 0x7FFFFFFF + typedef int blargg_long; +#else + typedef long blargg_long; +#endif + +#if UINT_MAX >= 0xFFFFFFFF + typedef unsigned blargg_ulong; +#else + typedef unsigned long blargg_ulong; +#endif + +// int8_t etc. + + +// ROCKBOX: If defined, use for int_8_t etc +#if defined (ROCKBOX) + #include +// HAVE_STDINT_H: If defined, use for int8_t etc. +#elif defined (HAVE_STDINT_H) + #include + #define BOOST + +// HAVE_INTTYPES_H: If defined, use for int8_t etc. +#elif defined (HAVE_INTTYPES_H) + #include + #define BOOST + +#else + #if UCHAR_MAX == 0xFF && SCHAR_MAX == 0x7F + typedef signed char int8_t; + typedef unsigned char uint8_t; + #else + // No suitable 8-bit type available + typedef struct see_blargg_common_h int8_t; + typedef struct see_blargg_common_h uint8_t; + #endif + + #if USHRT_MAX == 0xFFFF + typedef short int16_t; + typedef unsigned short uint16_t; + #else + // No suitable 16-bit type available + typedef struct see_blargg_common_h int16_t; + typedef struct see_blargg_common_h uint16_t; + #endif + + #if ULONG_MAX == 0xFFFFFFFF + typedef long int32_t; + typedef unsigned long uint32_t; + #elif UINT_MAX == 0xFFFFFFFF + typedef int int32_t; + typedef unsigned int uint32_t; + #else + // No suitable 32-bit type available + typedef struct see_blargg_common_h int32_t; + typedef struct see_blargg_common_h uint32_t; + #endif +#endif + +#endif +#endif diff --git a/apps/codecs/libgme/blargg_config.h b/apps/codecs/libgme/blargg_config.h new file mode 100644 index 0000000000..6490c15cfb --- /dev/null +++ b/apps/codecs/libgme/blargg_config.h @@ -0,0 +1,42 @@ +// Library configuration. Modify this file as necessary. + +#ifndef BLARGG_CONFIG_H +#define BLARGG_CONFIG_H + +// Uncomment to enable platform-specific optimizations +//#define BLARGG_NONPORTABLE 1 + +// Uncomment if automatic byte-order determination doesn't work +#ifdef ROCKBOX_BIG_ENDIAN + #define BLARGG_BIG_ENDIAN 1 +#endif + +// Uncomment if you get errors in the bool section of blargg_common.h +#define BLARGG_COMPILER_HAS_BOOL 1 + +// Uncomment to use fast gb apu implementation +// #define GB_APU_FAST 1 + +// Uncomment to remove agb emulation support +// #define GB_APU_NO_AGB 1 + +// Uncomment to emulate only nes apu +// #define NSF_EMU_APU_ONLY 1 + +// Uncomment to remove vrc7 apu support +// #define NSF_EMU_NO_VRC7 1 + +// Uncomment to remove fmopl apu support +// #define KSS_EMU_NO_FMOPL 1 + +// To handle undefined reference to assert +#define NDEBUG 1 + +// Use standard config.h if present +#define HAVE_CONFIG_H 1 + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#endif diff --git a/apps/codecs/libgme/blargg_endian.h b/apps/codecs/libgme/blargg_endian.h new file mode 100644 index 0000000000..ae55d7fd3b --- /dev/null +++ b/apps/codecs/libgme/blargg_endian.h @@ -0,0 +1,147 @@ +// CPU Byte Order Utilities + +// Game_Music_Emu 0.5.2 +#ifndef BLARGG_ENDIAN +#define BLARGG_ENDIAN + +#include "blargg_common.h" + +// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16) +#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) || defined (__i386__) + #define BLARGG_CPU_X86 1 + #define BLARGG_CPU_CISC 1 +#endif + +#if defined (__powerpc__) || defined (__ppc__) || defined (__POWERPC__) || defined (__powerc) + #define BLARGG_CPU_POWERPC 1 +#endif + +// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only +// one may be #defined to 1. Only needed if something actually depends on byte order. +#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN) +#ifdef __GLIBC__ + // GCC handles this for us + #include + #if __BYTE_ORDER == __LITTLE_ENDIAN + #define BLARGG_LITTLE_ENDIAN 1 + #elif __BYTE_ORDER == __BIG_ENDIAN + #define BLARGG_BIG_ENDIAN 1 + #endif +#else + +#if defined (LSB_FIRST) || defined (__LITTLE_ENDIAN__) || defined (BLARGG_CPU_X86) || \ + (defined (LITTLE_ENDIAN) && LITTLE_ENDIAN+0 != 1234) + #define BLARGG_LITTLE_ENDIAN 1 +#endif + +#if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \ + defined (__mips__) || defined (__sparc__) || defined (BLARGG_CPU_POWERPC) || \ + (defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321) + #define BLARGG_BIG_ENDIAN 1 +#else + // No endian specified; assume little-endian, since it's most common + #define BLARGG_LITTLE_ENDIAN 1 +#endif +#endif +#endif + +#if defined (BLARGG_LITTLE_ENDIAN) && defined(BLARGG_BIG_ENDIAN) + #undef BLARGG_LITTLE_ENDIAN + #undef BLARGG_BIG_ENDIAN +#endif + +static inline void blargg_verify_byte_order( void ) +{ + #ifndef NDEBUG + #if BLARGG_BIG_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i == 0 ); + #elif BLARGG_LITTLE_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i != 0 ); + #endif + #endif +} + +static inline unsigned get_le16( void const* p ) { + return ((unsigned char const*) p) [1] * 0x100u + + ((unsigned char const*) p) [0]; +} +static inline unsigned get_be16( void const* p ) { + return ((unsigned char const*) p) [0] * 0x100u + + ((unsigned char const*) p) [1]; +} +static inline blargg_ulong get_le32( void const* p ) { + return ((unsigned char const*) p) [3] * 0x01000000u + + ((unsigned char const*) p) [2] * 0x00010000u + + ((unsigned char const*) p) [1] * 0x00000100u + + ((unsigned char const*) p) [0]; +} +static inline blargg_ulong get_be32( void const* p ) { + return ((unsigned char const*) p) [0] * 0x01000000u + + ((unsigned char const*) p) [1] * 0x00010000u + + ((unsigned char const*) p) [2] * 0x00000100u + + ((unsigned char const*) p) [3]; +} +static inline void set_le16( void* p, unsigned n ) { + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [0] = (unsigned char) n; +} +static inline void set_be16( void* p, unsigned n ) { + ((unsigned char*) p) [0] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) n; +} +static inline void set_le32( void* p, blargg_ulong n ) { + ((unsigned char*) p) [3] = (unsigned char) (n >> 24); + ((unsigned char*) p) [2] = (unsigned char) (n >> 16); + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [0] = (unsigned char) n; +} +static inline void set_be32( void* p, blargg_ulong n ) { + ((unsigned char*) p) [0] = (unsigned char) (n >> 24); + ((unsigned char*) p) [1] = (unsigned char) (n >> 16); + ((unsigned char*) p) [2] = (unsigned char) (n >> 8); + ((unsigned char*) p) [3] = (unsigned char) n; +} + +#if defined(BLARGG_NONPORTABLE) + // Optimized implementation if byte order is known + #if defined(BLARGG_LITTLE_ENDIAN) + #define GET_LE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_LE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + #elif defined(BLARGG_BIG_ENDIAN) + #define GET_BE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_BE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + #endif + + #if defined(BLARGG_CPU_POWERPC) && defined (__MWERKS__) + // PowerPC has special byte-reversed instructions + // to do: assumes that PowerPC is running in big-endian mode + // to do: implement for other compilers which don't support these macros + #define GET_LE16( addr ) (__lhbrx( (addr), 0 )) + #define GET_LE32( addr ) (__lwbrx( (addr), 0 )) + #define SET_LE16( addr, data ) (__sthbrx( (data), (addr), 0 )) + #define SET_LE32( addr, data ) (__stwbrx( (data), (addr), 0 )) + #endif +#endif + +#ifndef GET_LE16 + #define GET_LE16( addr ) get_le16( addr ) + #define GET_LE32( addr ) get_le32( addr ) + #define SET_LE16( addr, data ) set_le16( addr, data ) + #define SET_LE32( addr, data ) set_le32( addr, data ) +#endif + +#ifndef GET_BE16 + #define GET_BE16( addr ) get_be16( addr ) + #define GET_BE32( addr ) get_be32( addr ) + #define SET_BE16( addr, data ) set_be16( addr, data ) + #define SET_BE32( addr, data ) set_be32( addr, data ) +#endif + +#endif diff --git a/apps/codecs/libgme/blargg_source.h b/apps/codecs/libgme/blargg_source.h new file mode 100644 index 0000000000..4bea02a48b --- /dev/null +++ b/apps/codecs/libgme/blargg_source.h @@ -0,0 +1,71 @@ +// Included at the beginning of library source files, after all other #include lines +#ifndef BLARGG_SOURCE_H +#define BLARGG_SOURCE_H + +// If debugging is enabled, abort program if expr is false. Meant for checking +// internal state and consistency. A failed assertion indicates a bug in the module. +// void assert( bool expr ); +#include + +// If debugging is enabled and expr is false, abort program. Meant for checking +// caller-supplied parameters and operations that are outside the control of the +// module. A failed requirement indicates a bug outside the module. +// void require( bool expr ); +#if defined(ROCKBOX) +#undef require +#define require( expr ) +#else +#undef require +#define require( expr ) assert( expr ) +#endif + +// Like printf() except output goes to debug log file. Might be defined to do +// nothing (not even evaluate its arguments). +// void dprintf( const char* format, ... ); +#if defined(ROCKBOX) +#define dprintf DEBUGF +#else +static inline void blargg_dprintf_( const char* fmt, ... ) { } +#undef dprintf +#define dprintf (1) ? (void) 0 : blargg_dprintf_ +#endif + +// If enabled, evaluate expr and if false, make debug log entry with source file +// and line. Meant for finding situations that should be examined further, but that +// don't indicate a problem. In all cases, execution continues normally. +#undef check +#define check( expr ) ((void) 0) + +// If expr yields error string, return it from current function, otherwise continue. +#undef RETURN_ERR +#define RETURN_ERR( expr ) do { \ + blargg_err_t blargg_return_err_ = (expr); \ + if ( blargg_return_err_ ) return blargg_return_err_; \ + } while ( 0 ) + +// If ptr is 0, return out of memory error string. +#undef CHECK_ALLOC +#define CHECK_ALLOC( ptr ) do { if ( (ptr) == 0 ) return "Out of memory"; } while ( 0 ) + +#ifndef max + #define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#ifndef min + #define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +// TODO: good idea? bad idea? +#undef byte +#define byte byte_ +typedef unsigned char byte; + +// deprecated +#define BLARGG_CHECK_ALLOC CHECK_ALLOC +#define BLARGG_RETURN_ERR RETURN_ERR + +// BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf and check +#ifdef BLARGG_SOURCE_BEGIN + #include BLARGG_SOURCE_BEGIN +#endif + +#endif diff --git a/apps/codecs/libgme/blip_buffer.c b/apps/codecs/libgme/blip_buffer.c new file mode 100644 index 0000000000..3061f68573 --- /dev/null +++ b/apps/codecs/libgme/blip_buffer.c @@ -0,0 +1,285 @@ +// Blip_Buffer 0.4.1. http://www.slack.net/~ant/ + +#include "blip_buffer.h" + +#include +#include +#include +#include +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +int const silent_buf_size = 1; // size used for Silent_Blip_Buffer + +void Blip_init( struct Blip_Buffer* this ) +{ + this->factor_ = LONG_MAX; + this->offset_ = 0; + this->buffer_size_ = 0; + this->sample_rate_ = 0; + this->reader_accum_ = 0; + this->bass_shift_ = 0; + this->clock_rate_ = 0; + this->bass_freq_ = 16; + this->length_ = 0; + + // assumptions code makes about implementation-defined features + #ifndef NDEBUG + // right shift of negative value preserves sign + buf_t_ i = -0x7FFFFFFE; + assert( (i >> 1) == -0x3FFFFFFF ); + + // casting to short truncates to 16 bits and sign-extends + i = 0x18000; + assert( (short) i == -0x8000 ); + #endif +} + +void Blip_stop( struct Blip_Buffer* this ) +{ + if ( this->buffer_size_ != silent_buf_size ) + free( this->buffer_ ); +} + +void Blip_clear( struct Blip_Buffer* this, int entire_buffer ) +{ + this->offset_ = 0; + this->reader_accum_ = 0; + this->modified_ = 0; + if ( this->buffer_ ) + { + long count = (entire_buffer ? this->buffer_size_ : Blip_samples_avail( this )); + memset( this->buffer_, 0, (count + blip_buffer_extra_) * sizeof (buf_t_) ); + } +} + +blargg_err_t Blip_set_sample_rate( struct Blip_Buffer* this, long new_rate, int msec ) +{ + if ( this->buffer_size_ == silent_buf_size ) + { + assert( 0 ); + return "Internal (tried to resize Silent_Blip_Buffer)"; + } + + // start with maximum length that resampled time can represent + long new_size = (ULONG_MAX >> BLIP_BUFFER_ACCURACY) - blip_buffer_extra_ - 64; + if ( msec != blip_max_length ) + { + long s = (new_rate * (msec + 1) + 999) / 1000; + if ( s < new_size ) + new_size = s; + else + assert( 0 ); // fails if requested buffer length exceeds limit + } + + if ( new_size > blip_buffer_max ) + return "Out of memory"; + + this->buffer_size_ = new_size; + assert( this->buffer_size_ != silent_buf_size ); + + // update things based on the sample rate + this->sample_rate_ = new_rate; + this->length_ = new_size * 1000 / new_rate - 1; + if ( msec ) + assert( this->length_ == msec ); // ensure length is same as that passed in + if ( this->clock_rate_ ) + Blip_set_clock_rate( this, this->clock_rate_ ); + Blip_bass_freq( this, this->bass_freq_ ); + + Blip_clear( this, 1 ); + + return 0; // success +} + +/* Not sure if this affects sound quality */ +#if defined(ROCKBOX) +double floor(double x) { + if ( x > 0 ) return (int)x; + return (int)(x-0.9999999999999999); +} +#endif + +blip_resampled_time_t Blip_clock_rate_factor( struct Blip_Buffer* this, long rate ) +{ + double ratio = (double) this->sample_rate_ / rate; + blip_long factor = (blip_long) floor( ratio * (1L << BLIP_BUFFER_ACCURACY) + 0.5 ); + assert( factor > 0 || !this->sample_rate_ ); // fails if clock/output ratio is too large + return (blip_resampled_time_t) factor; +} + +void Blip_bass_freq( struct Blip_Buffer* this, int freq ) +{ + this->bass_freq_ = freq; + int shift = 31; + if ( freq > 0 ) + { + shift = 13; + long f = (freq << 16) / this->sample_rate_; + while ( (f >>= 1) && --shift ) { } + } + this->bass_shift_ = shift; +} + +void Blip_end_frame( struct Blip_Buffer* this, blip_time_t t ) +{ + this->offset_ += t * this->factor_; + assert( Blip_samples_avail( this ) <= (long) this->buffer_size_ ); // time outside buffer length +} + +void Blip_remove_silence( struct Blip_Buffer* this, long count ) +{ + assert( count <= Blip_samples_avail( this ) ); // tried to remove more samples than available + this->offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; +} + +long Blip_count_samples( struct Blip_Buffer* this, blip_time_t t ) +{ + unsigned long last_sample = Blip_resampled_time( this, t ) >> BLIP_BUFFER_ACCURACY; + unsigned long first_sample = this->offset_ >> BLIP_BUFFER_ACCURACY; + return (long) (last_sample - first_sample); +} + +blip_time_t Blip_count_clocks( struct Blip_Buffer* this, long count ) +{ + if ( !this->factor_ ) + { + assert( 0 ); // sample rate and clock rates must be set first + return 0; + } + + if ( count > this->buffer_size_ ) + count = this->buffer_size_; + blip_resampled_time_t time = (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; + return (blip_time_t) ((time - this->offset_ + this->factor_ - 1) / this->factor_); +} + +void Blip_remove_samples( struct Blip_Buffer* this, long count ) +{ + if ( count ) + { + Blip_remove_silence( this, count ); + + // copy remaining samples to beginning and clear old samples + long remain = Blip_samples_avail( this ) + blip_buffer_extra_; + memmove( this->buffer_, this->buffer_ + count, remain * sizeof *this->buffer_ ); + memset( this->buffer_ + remain, 0, count * sizeof *this->buffer_ ); + } +} + +long Blip_read_samples( struct Blip_Buffer* this, blip_sample_t* BLIP_RESTRICT out, long max_samples, int stereo ) +{ + long count = Blip_samples_avail( this ); + if ( count > max_samples ) + count = max_samples; + + if ( count ) + { + int const bass = BLIP_READER_BASS( *this ); + BLIP_READER_BEGIN( reader, *this ); + + if ( !stereo ) + { + blip_long n; + for ( n = count; n; --n ) + { + blip_long s = BLIP_READER_READ( reader ); + if ( (blip_sample_t) s != s ) + s = 0x7FFF - (s >> 24); + *out++ = (blip_sample_t) s; + BLIP_READER_NEXT( reader, bass ); + } + } + else + { + blip_long n; + for ( n = count; n; --n ) + { + blip_long s = BLIP_READER_READ( reader ); + if ( (blip_sample_t) s != s ) + s = 0x7FFF - (s >> 24); + *out = (blip_sample_t) s; + out += 2; + BLIP_READER_NEXT( reader, bass ); + } + } + BLIP_READER_END( reader, *this ); + + Blip_remove_samples( this, count ); + } + return count; +} + +void Blip_mix_samples( struct Blip_Buffer* this, blip_sample_t const* in, long count ) +{ + if ( this->buffer_size_ == silent_buf_size ) + { + assert( 0 ); + return; + } + + buf_t_* out = this->buffer_ + (this->offset_ >> BLIP_BUFFER_ACCURACY) + blip_widest_impulse_ / 2; + + int const sample_shift = blip_sample_bits - 16; + int prev = 0; + while ( count-- ) + { + blip_long s = (blip_long) *in++ << sample_shift; + *out += s - prev; + prev = s; + ++out; + } + *out -= prev; +} + +void Blip_set_modified( struct Blip_Buffer* this ) +{ + this->modified_ = 1; +} + +int Blip_clear_modified( struct Blip_Buffer* this ) +{ + int b = this->modified_; + this->modified_ = 0; + return b; +} + +blip_resampled_time_t Blip_resampled_duration( struct Blip_Buffer* this, int t ) +{ + return t * this->factor_; +} + +blip_resampled_time_t Blip_resampled_time( struct Blip_Buffer* this, blip_time_t t ) +{ + return t * this->factor_ + this->offset_; +} + + +// Blip_Synth + +void Synth_init( struct Blip_Synth* this ) +{ + this->buf = 0; + this->last_amp = 0; + this->delta_factor = 0; +} + +// Set overall volume of waveform +void Synth_volume( struct Blip_Synth* this, double v ) +{ + this->delta_factor = (int) (v * (1L << blip_sample_bits) + 0.5); +} diff --git a/apps/codecs/libgme/blip_buffer.h b/apps/codecs/libgme/blip_buffer.h new file mode 100644 index 0000000000..84ed6e6690 --- /dev/null +++ b/apps/codecs/libgme/blip_buffer.h @@ -0,0 +1,279 @@ +// Band-limited sound synthesis buffer + +// Blip_Buffer 0.4.1 +#ifndef BLIP_BUFFER_H +#define BLIP_BUFFER_H + +#include + + // internal + #include "blargg_common.h" + #if INT_MAX >= 0x7FFFFFFF + typedef int blip_long; + typedef unsigned blip_ulong; + #else + typedef long blip_long; + typedef unsigned long blip_ulong; + #endif + +// Time unit at source clock rate +typedef blip_long blip_time_t; + +// Number of bits in resample ratio fraction. Higher values give a more accurate ratio +// but reduce maximum buffer size. +#ifndef BLIP_BUFFER_ACCURACY + #define BLIP_BUFFER_ACCURACY 16 +#endif + +// Number bits in phase offset. Fewer than 6 bits (64 phase offsets) results in +// noticeable broadband noise when synthesizing high frequency square waves. +// Affects size of Blip_Synth objects since they store the waveform directly. +#ifndef BLIP_PHASE_BITS + #define BLIP_PHASE_BITS 8 +#endif + +// Output samples are 16-bit signed, with a range of -32768 to 32767 +typedef short blip_sample_t; +enum { blip_sample_max = 32767 }; +enum { blip_widest_impulse_ = 16 }; +enum { blip_buffer_extra_ = blip_widest_impulse_ + 2 }; +enum { blip_res = 1 << BLIP_PHASE_BITS }; +enum { blip_max_length = 0 }; +enum { blip_default_length = 250 }; + +// Maximun buffer size (48Khz, 50 ms) +enum { blip_buffer_max = 2466 }; +enum { blip_sample_bits = 30 }; + +typedef blip_time_t buf_t_; +/* typedef const char* blargg_err_t; */ +typedef blip_ulong blip_resampled_time_t; + +struct Blip_Buffer { + blip_ulong factor_; + blip_resampled_time_t offset_; + buf_t_ buffer_ [blip_buffer_max]; + blip_long buffer_size_; + blip_long reader_accum_; + int bass_shift_; + + long sample_rate_; + long clock_rate_; + int bass_freq_; + int length_; + int modified_; +}; + +// not documented yet +void Blip_set_modified( struct Blip_Buffer* this ) ICODE_ATTR; +int Blip_clear_modified( struct Blip_Buffer* this ) ICODE_ATTR; +void Blip_remove_silence( struct Blip_Buffer* this, long count ) ICODE_ATTR; +blip_resampled_time_t Blip_resampled_duration( struct Blip_Buffer* this, int t ) ICODE_ATTR; +blip_resampled_time_t Blip_resampled_time( struct Blip_Buffer* this, blip_time_t t ) ICODE_ATTR; +blip_resampled_time_t Blip_clock_rate_factor( struct Blip_Buffer* this, long clock_rate ) ICODE_ATTR; + +// Initializes Blip_Buffer structure +void Blip_init( struct Blip_Buffer* this ); + +// Stops (clear) Blip_Buffer structure +void Blip_stop( struct Blip_Buffer* this ); + +// Set output sample rate and buffer length in milliseconds (1/1000 sec, defaults +// to 1/4 second), then clear buffer. Returns NULL on success, otherwise if there +// isn't enough memory, returns error without affecting current buffer setup. +blargg_err_t Blip_set_sample_rate( struct Blip_Buffer* this, long samples_per_sec, int msec_length ); + +// Set number of source time units per second +static inline void Blip_set_clock_rate( struct Blip_Buffer* this, long cps ) +{ + this->factor_ = Blip_clock_rate_factor( this, this->clock_rate_ = cps ); +} + +// End current time frame of specified duration and make its samples available +// (along with any still-unread samples) for reading with read_samples(). Begins +// a new time frame at the end of the current frame. +void Blip_end_frame( struct Blip_Buffer* this, blip_time_t time ) ICODE_ATTR; + +// Read at most 'max_samples' out of buffer into 'dest', removing them from from +// the buffer. Returns number of samples actually read and removed. If stereo is +// true, increments 'dest' one extra time after writing each sample, to allow +// easy interleving of two channels into a stereo output buffer. +long Blip_read_samples( struct Blip_Buffer* this, blip_sample_t* dest, long max_samples, int stereo ) ICODE_ATTR; + +// Additional optional features + +// Current output sample rate +static inline long Blip_sample_rate( struct Blip_Buffer* this ) +{ + return this->sample_rate_; +} + +// Length of buffer, in milliseconds +static inline int Blip_length( struct Blip_Buffer* this ) +{ + return this->length_; +} + +// Number of source time units per second +static inline long Blip_clock_rate( struct Blip_Buffer* this ) +{ + return this->clock_rate_; +} + + +// Set frequency high-pass filter frequency, where higher values reduce bass more +void Blip_bass_freq( struct Blip_Buffer* this, int frequency ); + +// Number of samples delay from synthesis to samples read out +static inline int Blip_output_latency( void ) +{ + return blip_widest_impulse_ / 2; +} + +// Remove all available samples and clear buffer to silence. If 'entire_buffer' is +// false, just clears out any samples waiting rather than the entire buffer. +void Blip_clear( struct Blip_Buffer* this, int entire_buffer ); + +// Number of samples available for reading with read_samples() +static inline long Blip_samples_avail( struct Blip_Buffer* this ) +{ + return (long) (this->offset_ >> BLIP_BUFFER_ACCURACY); +} + +// Remove 'count' samples from those waiting to be read +void Blip_remove_samples( struct Blip_Buffer* this, long count ) ICODE_ATTR; + +// Experimental features + +// Count number of clocks needed until 'count' samples will be available. +// If buffer can't even hold 'count' samples, returns number of clocks until +// buffer becomes full. +blip_time_t Blip_count_clocks( struct Blip_Buffer* this, long count ) ICODE_ATTR; + +// Number of raw samples that can be mixed within frame of specified duration. +long Blip_count_samples( struct Blip_Buffer* this, blip_time_t duration ) ICODE_ATTR; + +// Mix 'count' samples from 'buf' into buffer. +void Blip_mix_samples( struct Blip_Buffer* this, blip_sample_t const* buf, long count ) ICODE_ATTR; + +// Range specifies the greatest expected change in amplitude. Calculate it +// by finding the difference between the maximum and minimum expected +// amplitudes (max - min). + +struct Blip_Synth { + struct Blip_Buffer* buf; + int last_amp; + int delta_factor; +}; + +// Initializes Blip_Synth structure +void Synth_init( struct Blip_Synth* this ); + +// Set overall volume of waveform +void Synth_volume( struct Blip_Synth* this, double v ) ICODE_ATTR; + +// Get/set Blip_Buffer used for output +const struct Blip_Buffer* Synth_output( struct Blip_Synth* this ) ICODE_ATTR; + +// Low-level interface + + #if defined (__GNUC__) || _MSC_VER >= 1100 + #define BLIP_RESTRICT __restrict + #else + #define BLIP_RESTRICT + #endif + +// Works directly in terms of fractional output samples. Contact author for more info. +static inline void Synth_offset_resampled( struct Blip_Synth* this, blip_resampled_time_t time, + int delta, struct Blip_Buffer* blip_buf ) +{ + // Fails if time is beyond end of Blip_Buffer, due to a bug in caller code or the + // need for a longer buffer as set by set_sample_rate(). + assert( (blip_long) (time >> BLIP_BUFFER_ACCURACY) < blip_buf->buffer_size_ ); + delta *= this->delta_factor; + blip_long* BLIP_RESTRICT buf = blip_buf->buffer_ + (time >> BLIP_BUFFER_ACCURACY); + int phase = (int) (time >> (BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS) & (blip_res - 1)); + + blip_long left = buf [0] + delta; + + // Kind of crappy, but doing shift after multiply results in overflow. + // Alternate way of delaying multiply by delta_factor results in worse + // sub-sample resolution. + blip_long right = (delta >> BLIP_PHASE_BITS) * phase; + left -= right; + right += buf [1]; + + buf [0] = left; + buf [1] = right; +} + +// Update amplitude of waveform at given time. Using this requires a separate +// Blip_Synth for each waveform. +static inline void Synth_update( struct Blip_Synth* this, blip_time_t t, int amp ) +{ + int delta = amp - this->last_amp; + this->last_amp = amp; + Synth_offset_resampled( this, t * this->buf->factor_ + this->buf->offset_, delta, this->buf ); +} + +// Add an amplitude transition of specified delta, optionally into specified buffer +// rather than the one set with output(). Delta can be positive or negative. +// The actual change in amplitude is delta * (volume / range) +static inline void Synth_offset( struct Blip_Synth* this, blip_time_t t, int delta, struct Blip_Buffer* buf ) +{ + Synth_offset_resampled( this, t * buf->factor_ + buf->offset_, delta, buf ); +} + +// Same as offset(), except code is inlined for higher performance +static inline void Synth_offset_inline( struct Blip_Synth* this, blip_time_t t, int delta, struct Blip_Buffer* buf ) +{ + Synth_offset_resampled( this, t * buf->factor_ + buf->offset_, delta, buf ); +} + +// Optimized reading from Blip_Buffer, for use in custom sample output + +// Begin reading from buffer. Name should be unique to the current block. +#define BLIP_READER_BEGIN( name, blip_buffer ) \ + buf_t_* BLIP_RESTRICT name##_reader_buf = (blip_buffer).buffer_;\ + blip_long name##_reader_accum = (blip_buffer).reader_accum_ + +// Get value to pass to BLIP_READER_NEXT() +#define BLIP_READER_BASS( blip_buffer ) ((blip_buffer).bass_shift_) + +// Current sample +#define BLIP_READER_READ( name ) (name##_reader_accum >> (blip_sample_bits - 16)) + +// Current raw sample in full internal resolution +#define BLIP_READER_READ_RAW( name ) (name##_reader_accum) + +// Advance to next sample +#define BLIP_READER_NEXT( name, bass ) \ + (void) (name##_reader_accum += *name##_reader_buf++ - (name##_reader_accum >> (bass))) + +// End reading samples from buffer. The number of samples read must now be removed +// using Blip_remove_samples(). +#define BLIP_READER_END( name, blip_buffer ) \ + (void) ((blip_buffer).reader_accum_ = name##_reader_accum) + +#define BLIP_READER_ADJ_( name, offset ) (name##_reader_buf += offset) + +#define BLIP_READER_NEXT_IDX_( name, bass, idx ) {\ + name##_reader_accum -= name##_reader_accum >> (bass);\ + name##_reader_accum += name##_reader_buf [(idx)];\ +} + +//// BLIP_CLAMP + +#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) || defined (__i386__) + #define BLIP_X86 1 + #define BLIP_CLAMP_( in ) in < -0x8000 || 0x7FFF < in +#else + #define BLIP_CLAMP_( in ) (blip_sample_t) in != in +#endif + +// Clamp sample to blip_sample_t range +#define BLIP_CLAMP( sample, out )\ + { if ( BLIP_CLAMP_( (sample) ) ) (out) = ((sample) >> 31) ^ 0x7FFF; } + +#endif diff --git a/apps/codecs/libgme/emu2413.c b/apps/codecs/libgme/emu2413.c new file mode 100644 index 0000000000..5def504cbd --- /dev/null +++ b/apps/codecs/libgme/emu2413.c @@ -0,0 +1,1958 @@ +/*********************************************************************************** + + emu2413.c -- YM2413 emulator written by Mitsutaka Okazaki 2001 + + 2001 01-08 : Version 0.10 -- 1st version. + 2001 01-15 : Version 0.20 -- semi-public version. + 2001 01-16 : Version 0.30 -- 1st public version. + 2001 01-17 : Version 0.31 -- Fixed bassdrum problem. + : Version 0.32 -- LPF implemented. + 2001 01-18 : Version 0.33 -- Fixed the drum problem, refine the mix-down method. + -- Fixed the LFO bug. + 2001 01-24 : Version 0.35 -- Fixed the drum problem, + support undocumented EG behavior. + 2001 02-02 : Version 0.38 -- Improved the performance. + Fixed the hi-hat and cymbal model. + Fixed the default percussive datas. + Noise reduction. + Fixed the feedback problem. + 2001 03-03 : Version 0.39 -- Fixed some drum bugs. + Improved the performance. + 2001 03-04 : Version 0.40 -- Improved the feedback. + Change the default table size. + Clock and Rate can be changed during play. + 2001 06-24 : Version 0.50 -- Improved the hi-hat and the cymbal tone. + Added VRC7 patch (OPLL_reset_patch is changed). + Fixed OPLL_reset() bug. + Added OPLL_setMask, OPLL_getMask and OPLL_toggleMask. + Added OPLL_writeIO. + 2001 09-28 : Version 0.51 -- Removed the noise table. + 2002 01-28 : Version 0.52 -- Added Stereo mode. + 2002 02-07 : Version 0.53 -- Fixed some drum bugs. + 2002 02-20 : Version 0.54 -- Added the best quality mode. + 2002 03-02 : Version 0.55 -- Removed OPLL_init & OPLL_close. + 2002 05-30 : Version 0.60 -- Fixed HH&CYM generator and all voice datas. + 2004 04-10 : Version 0.61 -- Added YMF281B tone (defined by Chabin). + + 2011 03-22 : --------------- Modified by gama to use precalculated tables. + + References: + fmopl.c -- 1999,2000 written by Tatsuyuki Satoh (MAME development). + fmopl.c(fixed) -- (C) 2002 Jarek Burczynski. + s_opl.c -- 2001 written by Mamiya (NEZplug development). + fmgen.cpp -- 1999,2000 written by cisc. + fmpac.ill -- 2000 created by NARUTO. + MSX-Datapack + YMU757 data sheet + YM2143 data sheet + +**************************************************************************************/ +#include +#include +#include +#include +#include "emu2413.h" + +#include "emutables.h" +#if !defined(ROCKBOX) + #define EMU2413_CALCUL_TABLES +#else + #define EMU2413_COMPACTION + #include "emutables.h" +#endif + +#if defined(EMU2413_COMPACTION) && !defined(ROCKBOX) +#define OPLL_TONE_NUM 1 +static unsigned char default_inst[OPLL_TONE_NUM][(16 + 3) * 16] = { + { +#include "2413tone.h" + } +}; +#else +#define OPLL_TONE_NUM 3 +static unsigned char default_inst[OPLL_TONE_NUM][(16 + 3) * 16] = { + { +#include "2413tone.h" + }, + { +#include "vrc7tone.h" + }, + { +#include "281btone.h" + } +}; +#endif + +/* Size of Sintable ( 8 -- 18 can be used. 9 recommended.) */ +#define PG_BITS 9 +#define PG_WIDTH (1<>(b)) + +/* Leave the lower b bit(s). */ +#define LOWBITS(c,b) ((c)&((1<<(b))-1)) + +/* Expand x which is s bits to d bits. */ +#define EXPAND_BITS(x,s,d) ((x)<<((d)-(s))) + +/* Expand x which is s bits to d bits and fill expanded bits '1' */ +#define EXPAND_BITS_X(x,s,d) (((x)<<((d)-(s)))|((1<<((d)-(s)))-1)) + +/* Adjust envelope speed which depends on sampling rate. */ +#define RATE_ADJUST(x) (rate==49716?(e_uint32)x:(e_uint32)((double)(x)*clk/72/rate + 0.5)) /* added 0.5 to round the value*/ + +#define MOD(o,x) (&(o)->slot[(x)<<1]) +#define CAR(o,x) (&(o)->slot[((x)<<1)|1]) + +#define BIT(s,b) (((s)>>(b))&1) + +/* Input clock */ +static e_uint32 clk = 844451141; +/* Sampling rate */ +static e_uint32 rate = 3354932; + +/* WaveTable for each envelope amp */ +static e_uint16 fullsintable[PG_WIDTH]; +static e_uint16 halfsintable[PG_WIDTH]; + +static e_uint16 *waveform[2] = { fullsintable, halfsintable }; + +/* LFO Table */ +static e_int32 pmtable[PM_PG_WIDTH]; +static e_int32 amtable[AM_PG_WIDTH]; + +/* Phase delta for LFO */ +static e_uint32 pm_dphase; +static e_uint32 am_dphase; + +/* dB to Liner table */ +static e_int16 DB2LIN_TABLE[(DB_MUTE + DB_MUTE) * 2]; + +/* Liner to Log curve conversion table (for Attack rate). */ +static e_uint16 AR_ADJUST_TABLE[1 << EG_BITS]; + +/* Empty voice data */ +static OPLL_PATCH null_patch = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + +/* Basic voice Data */ +static OPLL_PATCH default_patch[OPLL_TONE_NUM][(16 + 3) * 2]; + +/* Definition of envelope mode */ +enum OPLL_EG_STATE +{ READY, ATTACK, DECAY, SUSHOLD, SUSTINE, RELEASE, SETTLE, FINISH }; + +/* Phase incr table for Attack */ +static e_uint32 dphaseARTable[16][16]; +/* Phase incr table for Decay and Release */ +static e_uint32 dphaseDRTable[16][16]; + +/* KSL + TL Table */ +static e_uint32 tllTable[16][8][1 << TL_BITS][4]; +static e_int32 rksTable[2][8][2]; + +/* We may not have too much SRAM in rockbox */ +#if !defined(ROCKBOX) +/* Phase incr table for PG */ +static e_uint32 dphaseTable[512][8][16]; +#endif + +/*************************************************** + + Create tables + +****************************************************/ +INLINE static e_int32 +Min (e_int32 i, e_int32 j) +{ + if (i < j) + return i; + else + return j; +} + +/* Table for AR to LogCurve. */ +static void +makeAdjustTable (void) +{ + e_int32 i; + + AR_ADJUST_TABLE[0] = (1 << EG_BITS) - 1; + for (i = 1; i < (1<= DB_MUTE) DB2LIN_TABLE[i] = 0; + DB2LIN_TABLE[i + DB_MUTE + DB_MUTE] = (e_int16) (-DB2LIN_TABLE[i]); + } +} + +#ifdef EMU2413_CALCUL_TABLES +/* Liner(+0.0 - +1.0) to dB((1<> (20 - DP_BITS)); +} +#endif + +static void +makeTllTable (void) +{ +#define dB2(x) ((x)*2) + + static double kltable[16] = { + dB2 (0.000), dB2 (9.000), dB2 (12.000), dB2 (13.875), dB2 (15.000), dB2 (16.125), dB2 (16.875), dB2 (17.625), + dB2 (18.000), dB2 (18.750), dB2 (19.125), dB2 (19.500), dB2 (19.875), dB2 (20.250), dB2 (20.625), dB2 (21.000) + }; + + e_int32 tmp; + e_int32 fnum, block, TL, KL; + + for (fnum = 0; fnum < 16; fnum++) + for (block = 0; block < 8; block++) + for (TL = 0; TL < 64; TL++) + for (KL = 0; KL < 4; KL++) + { + if (KL == 0) + { + tllTable[fnum][block][TL][KL] = TL2EG (TL); + } + else + { + tmp = (e_int32) (kltable[fnum] - dB2 (3.000) * (7 - block)); + if (tmp <= 0) + tllTable[fnum][block][TL][KL] = TL2EG (TL); + else + tllTable[fnum][block][TL][KL] = (e_uint32) ((tmp >> (3 - KL)) / EG_STEP) + TL2EG (TL); + } + } +} + +#ifdef USE_SPEC_ENV_SPEED +static double attacktime[16][4] = { + {0, 0, 0, 0}, + {1730.15, 1400.60, 1153.43, 988.66}, + {865.08, 700.30, 576.72, 494.33}, + {432.54, 350.15, 288.36, 247.16}, + {216.27, 175.07, 144.18, 123.58}, + {108.13, 87.54, 72.09, 61.79}, + {54.07, 43.77, 36.04, 30.90}, + {27.03, 21.88, 18.02, 15.45}, + {13.52, 10.94, 9.01, 7.72}, + {6.76, 5.47, 4.51, 3.86}, + {3.38, 2.74, 2.25, 1.93}, + {1.69, 1.37, 1.13, 0.97}, + {0.84, 0.70, 0.60, 0.54}, + {0.50, 0.42, 0.34, 0.30}, + {0.28, 0.22, 0.18, 0.14}, + {0.00, 0.00, 0.00, 0.00} +}; + +static double decaytime[16][4] = { + {0, 0, 0, 0}, + {20926.60, 16807.20, 14006.00, 12028.60}, + {10463.30, 8403.58, 7002.98, 6014.32}, + {5231.64, 4201.79, 3501.49, 3007.16}, + {2615.82, 2100.89, 1750.75, 1503.58}, + {1307.91, 1050.45, 875.37, 751.79}, + {653.95, 525.22, 437.69, 375.90}, + {326.98, 262.61, 218.84, 187.95}, + {163.49, 131.31, 109.42, 93.97}, + {81.74, 65.65, 54.71, 46.99}, + {40.87, 32.83, 27.36, 23.49}, + {20.44, 16.41, 13.68, 11.75}, + {10.22, 8.21, 6.84, 5.87}, + {5.11, 4.10, 3.42, 2.94}, + {2.55, 2.05, 1.71, 1.47}, + {1.27, 1.27, 1.27, 1.27} +}; +#endif + +/* Rate Table for Attack */ +static void +makeDphaseARTable (void) +{ + e_int32 AR, Rks, RM, RL; + +#ifdef USE_SPEC_ENV_SPEED + e_uint32 attacktable[16][4]; + + for (RM = 0; RM < 16; RM++) + for (RL = 0; RL < 4; RL++) + { + if (RM == 0) + attacktable[RM][RL] = 0; + else if (RM == 15) + attacktable[RM][RL] = EG_DP_WIDTH; + else + attacktable[RM][RL] = (e_uint32) ((double) (1 << EG_DP_BITS) / (attacktime[RM][RL] * 3579545 / 72000)); + + } +#endif + + for (AR = 0; AR < 16; AR++) + for (Rks = 0; Rks < 16; Rks++) + { + RM = AR + (Rks >> 2); + RL = Rks & 3; + if (RM > 15) + RM = 15; + switch (AR) + { + case 0: + dphaseARTable[AR][Rks] = 0; + break; + case 15: + dphaseARTable[AR][Rks] = 0;/*EG_DP_WIDTH;*/ + break; + default: +#ifdef USE_SPEC_ENV_SPEED + dphaseARTable[AR][Rks] = RATE_ADJUST (attacktable[RM][RL]); +#else + dphaseARTable[AR][Rks] = RATE_ADJUST ((3 * (RL + 4) << (RM + 1))); +#endif + break; + } + } +} + +/* Rate Table for Decay and Release */ +static void +makeDphaseDRTable (void) +{ + e_int32 DR, Rks, RM, RL; + +#ifdef USE_SPEC_ENV_SPEED + e_uint32 decaytable[16][4]; + + for (RM = 0; RM < 16; RM++) + for (RL = 0; RL < 4; RL++) + if (RM == 0) + decaytable[RM][RL] = 0; + else + decaytable[RM][RL] = (e_uint32) ((double) (1 << EG_DP_BITS) / (decaytime[RM][RL] * 3579545 / 72000)); +#endif + + for (DR = 0; DR < 16; DR++) + for (Rks = 0; Rks < 16; Rks++) + { + RM = DR + (Rks >> 2); + RL = Rks & 3; + if (RM > 15) + RM = 15; + switch (DR) + { + case 0: + dphaseDRTable[DR][Rks] = 0; + break; + default: +#ifdef USE_SPEC_ENV_SPEED + dphaseDRTable[DR][Rks] = RATE_ADJUST (decaytable[RM][RL]); +#else + dphaseDRTable[DR][Rks] = RATE_ADJUST ((RL + 4) << (RM - 1)); +#endif + break; + } + } +} + +static void +makeRksTable (void) +{ + + e_int32 fnum8, block, KR; + + for (fnum8 = 0; fnum8 < 2; fnum8++) + for (block = 0; block < 8; block++) + for (KR = 0; KR < 2; KR++) + { + if (KR != 0) + rksTable[fnum8][block][KR] = (block << 1) + fnum8; + else + rksTable[fnum8][block][KR] = block >> 1; + } +} + +void +OPLL_dump2patch (const e_uint8 * dump, OPLL_PATCH * patch) +{ + patch[0].AM = (dump[0] >> 7) & 1; + patch[1].AM = (dump[1] >> 7) & 1; + patch[0].PM = (dump[0] >> 6) & 1; + patch[1].PM = (dump[1] >> 6) & 1; + patch[0].EG = (dump[0] >> 5) & 1; + patch[1].EG = (dump[1] >> 5) & 1; + patch[0].KR = (dump[0] >> 4) & 1; + patch[1].KR = (dump[1] >> 4) & 1; + patch[0].ML = (dump[0]) & 15; + patch[1].ML = (dump[1]) & 15; + patch[0].KL = (dump[2] >> 6) & 3; + patch[1].KL = (dump[3] >> 6) & 3; + patch[0].TL = (dump[2]) & 63; + patch[0].FB = (dump[3]) & 7; + patch[0].WF = (dump[3] >> 3) & 1; + patch[1].WF = (dump[3] >> 4) & 1; + patch[0].AR = (dump[4] >> 4) & 15; + patch[1].AR = (dump[5] >> 4) & 15; + patch[0].DR = (dump[4]) & 15; + patch[1].DR = (dump[5]) & 15; + patch[0].SL = (dump[6] >> 4) & 15; + patch[1].SL = (dump[7] >> 4) & 15; + patch[0].RR = (dump[6]) & 15; + patch[1].RR = (dump[7]) & 15; +} + +void +OPLL_getDefaultPatch (e_int32 type, e_int32 num, OPLL_PATCH * patch) +{ + OPLL_dump2patch (default_inst[type] + num * 16, patch); +} + +static void +makeDefaultPatch ( void ) +{ + e_int32 i, j; + + for (i = 0; i < OPLL_TONE_NUM; i++) + for (j = 0; j < 19; j++) + OPLL_getDefaultPatch (i, j, &default_patch[i][j * 2]); + +} + +void +OPLL_setPatch (OPLL * opll, const e_uint8 * dump) +{ + OPLL_PATCH patch[2]; + int i; + + for (i = 0; i < 19; i++) + { + OPLL_dump2patch (dump + i * 16, patch); + memcpy (&opll->patch[i*2+0], &patch[0], sizeof (OPLL_PATCH)); + memcpy (&opll->patch[i*2+1], &patch[1], sizeof (OPLL_PATCH)); + } +} + +void +OPLL_patch2dump (const OPLL_PATCH * patch, e_uint8 * dump) +{ + dump[0] = (e_uint8) ((patch[0].AM << 7) + (patch[0].PM << 6) + (patch[0].EG << 5) + (patch[0].KR << 4) + patch[0].ML); + dump[1] = (e_uint8) ((patch[1].AM << 7) + (patch[1].PM << 6) + (patch[1].EG << 5) + (patch[1].KR << 4) + patch[1].ML); + dump[2] = (e_uint8) ((patch[0].KL << 6) + patch[0].TL); + dump[3] = (e_uint8) ((patch[1].KL << 6) + (patch[1].WF << 4) + (patch[0].WF << 3) + patch[0].FB); + dump[4] = (e_uint8) ((patch[0].AR << 4) + patch[0].DR); + dump[5] = (e_uint8) ((patch[1].AR << 4) + patch[1].DR); + dump[6] = (e_uint8) ((patch[0].SL << 4) + patch[0].RR); + dump[7] = (e_uint8) ((patch[1].SL << 4) + patch[1].RR); + dump[8] = 0; + dump[9] = 0; + dump[10] = 0; + dump[11] = 0; + dump[12] = 0; + dump[13] = 0; + dump[14] = 0; + dump[15] = 0; +} + +/************************************************************ + + Calc Parameters + +************************************************************/ + +INLINE static e_uint32 +calc_eg_dphase (OPLL_SLOT * slot) +{ + + switch (slot->eg_mode) + { + case ATTACK: + return dphaseARTable[slot->patch->AR][slot->rks]; + + case DECAY: + return dphaseDRTable[slot->patch->DR][slot->rks]; + + case SUSHOLD: + return 0; + + case SUSTINE: + return dphaseDRTable[slot->patch->RR][slot->rks]; + + case RELEASE: + if (slot->sustine) + return dphaseDRTable[5][slot->rks]; + else if (slot->patch->EG) + return dphaseDRTable[slot->patch->RR][slot->rks]; + else + return dphaseDRTable[7][slot->rks]; + + case SETTLE: + return dphaseDRTable[15][0]; + + case FINISH: + return 0; + + default: + return 0; + } +} + +/************************************************************* + + OPLL internal interfaces + +*************************************************************/ +#define SLOT_BD1 12 +#define SLOT_BD2 13 +#define SLOT_HH 14 +#define SLOT_SD 15 +#define SLOT_TOM 16 +#define SLOT_CYM 17 + +/* We will set this dinamically, but not sure if this affects playback */ +#if defined(ROCKBOX) +INLINE static void +UPDATE_PG(OPLL_SLOT * slot) +{ + static const e_uint32 mltable[16] = + { 1, 1 * 2, 2 * 2, 3 * 2, 4 * 2, 5 * 2, 6 * 2, 7 * 2, 8 * 2, 9 * 2, 10 * 2, 10 * 2, 12 * 2, 12 * 2, 15 * 2, 15 * 2 }; + + slot->dphase = RATE_ADJUST (((slot->fnum * mltable[slot->patch->ML]) << slot->block) >> (20 - DP_BITS)); +} +#else +#define UPDATE_PG(S) (S)->dphase = dphaseTable[(S)->fnum][(S)->block][(S)->patch->ML] +#endif + +#define UPDATE_TLL(S)\ +(((S)->type==0)?\ +((S)->tll = tllTable[((S)->fnum)>>5][(S)->block][(S)->patch->TL][(S)->patch->KL]):\ +((S)->tll = tllTable[((S)->fnum)>>5][(S)->block][(S)->volume][(S)->patch->KL])) +#define UPDATE_RKS(S) (S)->rks = rksTable[((S)->fnum)>>8][(S)->block][(S)->patch->KR] +#define UPDATE_WF(S) (S)->sintbl = waveform[(S)->patch->WF] +#define UPDATE_EG(S) (S)->eg_dphase = calc_eg_dphase(S) +#define UPDATE_ALL(S)\ + UPDATE_PG(S);\ + UPDATE_TLL(S);\ + UPDATE_RKS(S);\ + UPDATE_WF(S); \ + UPDATE_EG(S) /* EG should be updated last. */ + + +/* Slot key on */ +INLINE static void +slotOn (OPLL_SLOT * slot) +{ + slot->eg_mode = ATTACK; + slot->eg_phase = 0; + slot->phase = 0; + UPDATE_EG(slot); +} + +/* Slot key on without reseting the phase */ +INLINE static void +slotOn2 (OPLL_SLOT * slot) +{ + slot->eg_mode = ATTACK; + slot->eg_phase = 0; + UPDATE_EG(slot); +} + +/* Slot key off */ +INLINE static void +slotOff (OPLL_SLOT * slot) +{ + if (slot->eg_mode == ATTACK) + slot->eg_phase = EXPAND_BITS (AR_ADJUST_TABLE[HIGHBITS (slot->eg_phase, EG_DP_BITS - EG_BITS)], EG_BITS, EG_DP_BITS); + slot->eg_mode = RELEASE; + UPDATE_EG(slot); +} + +/* Channel key on */ +INLINE static void +keyOn (OPLL * opll, e_int32 i) +{ + if (!opll->slot_on_flag[i * 2]) + slotOn (MOD(opll,i)); + if (!opll->slot_on_flag[i * 2 + 1]) + slotOn (CAR(opll,i)); + opll->key_status[i] = 1; +} + +/* Channel key off */ +INLINE static void +keyOff (OPLL * opll, e_int32 i) +{ + if (opll->slot_on_flag[i * 2 + 1]) + slotOff (CAR(opll,i)); + opll->key_status[i] = 0; +} + +INLINE static void +keyOn_BD (OPLL * opll) +{ + keyOn (opll, 6); +} +INLINE static void +keyOn_SD (OPLL * opll) +{ + if (!opll->slot_on_flag[SLOT_SD]) + slotOn (CAR(opll,7)); +} +INLINE static void +keyOn_TOM (OPLL * opll) +{ + if (!opll->slot_on_flag[SLOT_TOM]) + slotOn (MOD(opll,8)); +} +INLINE static void +keyOn_HH (OPLL * opll) +{ + if (!opll->slot_on_flag[SLOT_HH]) + slotOn2 (MOD(opll,7)); +} +INLINE static void +keyOn_CYM (OPLL * opll) +{ + if (!opll->slot_on_flag[SLOT_CYM]) + slotOn2 (CAR(opll,8)); +} + +/* Drum key off */ +INLINE static void +keyOff_BD (OPLL * opll) +{ + keyOff (opll, 6); +} +INLINE static void +keyOff_SD (OPLL * opll) +{ + if (opll->slot_on_flag[SLOT_SD]) + slotOff (CAR(opll,7)); +} +INLINE static void +keyOff_TOM (OPLL * opll) +{ + if (opll->slot_on_flag[SLOT_TOM]) + slotOff (MOD(opll,8)); +} +INLINE static void +keyOff_HH (OPLL * opll) +{ + if (opll->slot_on_flag[SLOT_HH]) + slotOff (MOD(opll,7)); +} +INLINE static void +keyOff_CYM (OPLL * opll) +{ + if (opll->slot_on_flag[SLOT_CYM]) + slotOff (CAR(opll,8)); +} + +/* Change a voice */ +INLINE static void +setPatch (OPLL * opll, e_int32 i, e_int32 num) +{ + opll->patch_number[i] = num; + MOD(opll,i)->patch = &opll->patch[num * 2 + 0]; + CAR(opll,i)->patch = &opll->patch[num * 2 + 1]; +} + +/* Change a rhythm voice */ +INLINE static void +setSlotPatch (OPLL_SLOT * slot, OPLL_PATCH * patch) +{ + slot->patch = patch; +} + +/* Set sustine parameter */ +INLINE static void +setSustine (OPLL * opll, e_int32 c, e_int32 sustine) +{ + CAR(opll,c)->sustine = sustine; + if (MOD(opll,c)->type) + MOD(opll,c)->sustine = sustine; +} + +/* Volume : 6bit ( Volume register << 2 ) */ +INLINE static void +setVolume (OPLL * opll, e_int32 c, e_int32 volume) +{ + CAR(opll,c)->volume = volume; +} + +INLINE static void +setSlotVolume (OPLL_SLOT * slot, e_int32 volume) +{ + slot->volume = volume; +} + +/* Set F-Number ( fnum : 9bit ) */ +INLINE static void +setFnumber (OPLL * opll, e_int32 c, e_int32 fnum) +{ + CAR(opll,c)->fnum = fnum; + MOD(opll,c)->fnum = fnum; +} + +/* Set Block data (block : 3bit ) */ +INLINE static void +setBlock (OPLL * opll, e_int32 c, e_int32 block) +{ + CAR(opll,c)->block = block; + MOD(opll,c)->block = block; +} + +/* Change Rhythm Mode */ +INLINE static void +update_rhythm_mode (OPLL * opll) +{ + if (opll->patch_number[6] & 0x10) + { + if (!(opll->slot_on_flag[SLOT_BD2] | (opll->reg[0x0e] & 32))) + { + opll->slot[SLOT_BD1].eg_mode = FINISH; + opll->slot[SLOT_BD2].eg_mode = FINISH; + setPatch (opll, 6, opll->reg[0x36] >> 4); + } + } + else if (opll->reg[0x0e] & 32) + { + opll->patch_number[6] = 16; + opll->slot[SLOT_BD1].eg_mode = FINISH; + opll->slot[SLOT_BD2].eg_mode = FINISH; + setSlotPatch (&opll->slot[SLOT_BD1], &opll->patch[16 * 2 + 0]); + setSlotPatch (&opll->slot[SLOT_BD2], &opll->patch[16 * 2 + 1]); + } + + if (opll->patch_number[7] & 0x10) + { + if (!((opll->slot_on_flag[SLOT_HH] && opll->slot_on_flag[SLOT_SD]) | (opll->reg[0x0e] & 32))) + { + opll->slot[SLOT_HH].type = 0; + opll->slot[SLOT_HH].eg_mode = FINISH; + opll->slot[SLOT_SD].eg_mode = FINISH; + setPatch (opll, 7, opll->reg[0x37] >> 4); + } + } + else if (opll->reg[0x0e] & 32) + { + opll->patch_number[7] = 17; + opll->slot[SLOT_HH].type = 1; + opll->slot[SLOT_HH].eg_mode = FINISH; + opll->slot[SLOT_SD].eg_mode = FINISH; + setSlotPatch (&opll->slot[SLOT_HH], &opll->patch[17 * 2 + 0]); + setSlotPatch (&opll->slot[SLOT_SD], &opll->patch[17 * 2 + 1]); + } + + if (opll->patch_number[8] & 0x10) + { + if (!((opll->slot_on_flag[SLOT_CYM] && opll->slot_on_flag[SLOT_TOM]) | (opll->reg[0x0e] & 32))) + { + opll->slot[SLOT_TOM].type = 0; + opll->slot[SLOT_TOM].eg_mode = FINISH; + opll->slot[SLOT_CYM].eg_mode = FINISH; + setPatch (opll, 8, opll->reg[0x38] >> 4); + } + } + else if (opll->reg[0x0e] & 32) + { + opll->patch_number[8] = 18; + opll->slot[SLOT_TOM].type = 1; + opll->slot[SLOT_TOM].eg_mode = FINISH; + opll->slot[SLOT_CYM].eg_mode = FINISH; + setSlotPatch (&opll->slot[SLOT_TOM], &opll->patch[18 * 2 + 0]); + setSlotPatch (&opll->slot[SLOT_CYM], &opll->patch[18 * 2 + 1]); + } +} + +INLINE static void +update_key_status (OPLL * opll) +{ + int ch; + + for (ch = 0; ch < 9; ch++) + opll->slot_on_flag[ch * 2] = opll->slot_on_flag[ch * 2 + 1] = (opll->reg[0x20 + ch]) & 0x10; + + if (opll->reg[0x0e] & 32) + { + opll->slot_on_flag[SLOT_BD1] |= (opll->reg[0x0e] & 0x10); + opll->slot_on_flag[SLOT_BD2] |= (opll->reg[0x0e] & 0x10); + opll->slot_on_flag[SLOT_SD] |= (opll->reg[0x0e] & 0x08); + opll->slot_on_flag[SLOT_HH] |= (opll->reg[0x0e] & 0x01); + opll->slot_on_flag[SLOT_TOM] |= (opll->reg[0x0e] & 0x04); + opll->slot_on_flag[SLOT_CYM] |= (opll->reg[0x0e] & 0x02); + } +} + +void +OPLL_copyPatch (OPLL * opll, e_int32 num, OPLL_PATCH * patch) +{ + memcpy (&opll->patch[num], patch, sizeof (OPLL_PATCH)); +} + +/*********************************************************** + + Initializing + +***********************************************************/ + +static void +OPLL_SLOT_reset (OPLL_SLOT * slot, int type) +{ + slot->type = type; + slot->sintbl = waveform[0]; + slot->phase = 0; + slot->dphase = 0; + slot->output[0] = 0; + slot->output[1] = 0; + slot->feedback = 0; + slot->eg_mode = FINISH; + slot->eg_phase = EG_DP_WIDTH; + slot->eg_dphase = 0; + slot->rks = 0; + slot->tll = 0; + slot->sustine = 0; + slot->fnum = 0; + slot->block = 0; + slot->volume = 0; + slot->pgout = 0; + slot->egout = 0; + slot->patch = &null_patch; +} + +static void +internal_refresh (void) +{ +#if !defined(ROCKBOX) + makeDphaseTable (); +#endif + makeDphaseARTable (); + makeDphaseDRTable (); + pm_dphase = (e_uint32) RATE_ADJUST (PM_SPEED * PM_DP_WIDTH / (clk / 72)); + am_dphase = (e_uint32) RATE_ADJUST (AM_SPEED * AM_DP_WIDTH / (clk / 72)); +} + +static void +maketables (e_uint32 c, e_uint32 r) +{ + if (c != clk) + { + clk = c; + makePmTable (); + makeAmTable (); + makeDB2LinTable (); + makeAdjustTable (); + makeTllTable (); + makeRksTable (); + makeSinTable (); + makeDefaultPatch (); + } + + if (r != rate) + { + rate = r; + internal_refresh (); + } +} + +void +OPLL_new (OPLL *opll, e_uint32 clk, e_uint32 rate) +{ + e_int32 i; + + maketables (clk, rate); + + memset(opll, 0, sizeof (OPLL)); + for (i = 0; i < 19 * 2; i++) + memcpy(&opll->patch[i],&null_patch,sizeof(OPLL_PATCH)); + + opll->mask = 0; + + OPLL_reset (opll); + OPLL_reset_patch (opll, 0); +} + + +void +OPLL_delete (OPLL * opll) +{ + (void) opll; +} + + +/* Reset patch datas by system default. */ +void +OPLL_reset_patch (OPLL * opll, e_int32 type) +{ + e_int32 i; + + for (i = 0; i < 19 * 2; i++) + OPLL_copyPatch (opll, i, &default_patch[type % OPLL_TONE_NUM][i]); +} + +/* Reset whole of OPLL except patch datas. */ +void +OPLL_reset (OPLL * opll) +{ + e_int32 i; + + if (!opll) + return; + + opll->adr = 0; + opll->out = 0; + + opll->pm_phase = 0; + opll->am_phase = 0; + + opll->noise_seed = 0xffff; + opll->mask = 0; + + for (i = 0; i <18; i++) + OPLL_SLOT_reset(&opll->slot[i], i%2); + + for (i = 0; i < 9; i++) + { + opll->key_status[i] = 0; + setPatch (opll, i, 0); + } + + for (i = 0; i < 0x40; i++) + OPLL_writeReg (opll, i, 0); + +#ifndef EMU2413_COMPACTION + opll->realstep = (e_uint32) ((1 << 31) / rate); + opll->opllstep = (e_uint32) ((1 << 31) / (clk / 72)); + opll->oplltime = 0; + for (i = 0; i < 14; i++) + opll->pan[i] = 2; + opll->sprev[0] = opll->sprev[1] = 0; + opll->snext[0] = opll->snext[1] = 0; +#endif +} + +/* Force Refresh (When external program changes some parameters). */ +void +OPLL_forceRefresh (OPLL * opll) +{ + e_int32 i; + + if (opll == NULL) + return; + + for (i = 0; i < 9; i++) + setPatch(opll,i,opll->patch_number[i]); + + for (i = 0; i < 18; i++) + { + UPDATE_PG (&opll->slot[i]); + UPDATE_RKS (&opll->slot[i]); + UPDATE_TLL (&opll->slot[i]); + UPDATE_WF (&opll->slot[i]); + UPDATE_EG (&opll->slot[i]); + } +} + +void +OPLL_set_rate (OPLL * opll, e_uint32 r) +{ + if (rate == r) return; + if (opll->quality) + rate = 49716; + else + rate = r; + internal_refresh (); + rate = r; +} + +void +OPLL_set_quality (OPLL * opll, e_uint32 q) +{ + opll->quality = q; + OPLL_set_rate (opll, rate); +} + +/********************************************************* + + Generate wave data + +*********************************************************/ +/* Convert Amp(0 to EG_HEIGHT) to Phase(0 to 2PI). */ +#if ( SLOT_AMP_BITS - PG_BITS ) > 0 +#define wave2_2pi(e) ( (e) >> ( SLOT_AMP_BITS - PG_BITS )) +#else +#define wave2_2pi(e) ( (e) << ( PG_BITS - SLOT_AMP_BITS )) +#endif + +/* Convert Amp(0 to EG_HEIGHT) to Phase(0 to 4PI). */ +#if ( SLOT_AMP_BITS - PG_BITS - 1 ) == 0 +#define wave2_4pi(e) (e) +#elif ( SLOT_AMP_BITS - PG_BITS - 1 ) > 0 +#define wave2_4pi(e) ( (e) >> ( SLOT_AMP_BITS - PG_BITS - 1 )) +#else +#define wave2_4pi(e) ( (e) << ( 1 + PG_BITS - SLOT_AMP_BITS )) +#endif + +/* Convert Amp(0 to EG_HEIGHT) to Phase(0 to 8PI). */ +#if ( SLOT_AMP_BITS - PG_BITS - 2 ) == 0 +#define wave2_8pi(e) (e) +#elif ( SLOT_AMP_BITS - PG_BITS - 2 ) > 0 +#define wave2_8pi(e) ( (e) >> ( SLOT_AMP_BITS - PG_BITS - 2 )) +#else +#define wave2_8pi(e) ( (e) << ( 2 + PG_BITS - SLOT_AMP_BITS )) +#endif + +/* Update AM, PM unit */ +static void +update_ampm (OPLL * opll) +{ + opll->pm_phase = (opll->pm_phase + pm_dphase) & (PM_DP_WIDTH - 1); + opll->am_phase = (opll->am_phase + am_dphase) & (AM_DP_WIDTH - 1); + opll->lfo_am = amtable[HIGHBITS (opll->am_phase, AM_DP_BITS - AM_PG_BITS)]; + opll->lfo_pm = pmtable[HIGHBITS (opll->pm_phase, PM_DP_BITS - PM_PG_BITS)]; +} + +/* PG */ +INLINE static void +calc_phase (OPLL_SLOT * slot, e_int32 lfo) +{ + if (slot->patch->PM) + slot->phase += (slot->dphase * lfo) >> PM_AMP_BITS; + else + slot->phase += slot->dphase; + + slot->phase &= (DP_WIDTH - 1); + + slot->pgout = HIGHBITS (slot->phase, DP_BASE_BITS); +} + +/* Update Noise unit */ +static void +update_noise (OPLL * opll) +{ + if(opll->noise_seed&1) opll->noise_seed ^= 0x8003020; + opll->noise_seed >>= 1; +} + +/* EG */ +static void +calc_envelope (OPLL_SLOT * slot, e_int32 lfo) +{ +#define S2E(x) (SL2EG((e_int32)(x/SL_STEP))<<(EG_DP_BITS-EG_BITS)) + + static e_uint32 SL[16] = { + S2E (0.0), S2E (3.0), S2E (6.0), S2E (9.0), S2E (12.0), S2E (15.0), S2E (18.0), S2E (21.0), + S2E (24.0), S2E (27.0), S2E (30.0), S2E (33.0), S2E (36.0), S2E (39.0), S2E (42.0), S2E (48.0) + }; + + e_uint32 egout; + + switch (slot->eg_mode) + { + case ATTACK: + egout = AR_ADJUST_TABLE[HIGHBITS (slot->eg_phase, EG_DP_BITS - EG_BITS)]; + slot->eg_phase += slot->eg_dphase; + if((EG_DP_WIDTH & slot->eg_phase)||(slot->patch->AR==15)) + { + egout = 0; + slot->eg_phase = 0; + slot->eg_mode = DECAY; + UPDATE_EG (slot); + } + break; + + case DECAY: + egout = HIGHBITS (slot->eg_phase, EG_DP_BITS - EG_BITS); + slot->eg_phase += slot->eg_dphase; + if (slot->eg_phase >= SL[slot->patch->SL]) + { + if (slot->patch->EG) + { + slot->eg_phase = SL[slot->patch->SL]; + slot->eg_mode = SUSHOLD; + UPDATE_EG (slot); + } + else + { + slot->eg_phase = SL[slot->patch->SL]; + slot->eg_mode = SUSTINE; + UPDATE_EG (slot); + } + } + break; + + case SUSHOLD: + egout = HIGHBITS (slot->eg_phase, EG_DP_BITS - EG_BITS); + if (slot->patch->EG == 0) + { + slot->eg_mode = SUSTINE; + UPDATE_EG (slot); + } + break; + + case SUSTINE: + case RELEASE: + egout = HIGHBITS (slot->eg_phase, EG_DP_BITS - EG_BITS); + slot->eg_phase += slot->eg_dphase; + if (egout >= (1 << EG_BITS)) + { + slot->eg_mode = FINISH; + egout = (1 << EG_BITS) - 1; + } + break; + + case SETTLE: + egout = HIGHBITS (slot->eg_phase, EG_DP_BITS - EG_BITS); + slot->eg_phase += slot->eg_dphase; + if (egout >= (1 << EG_BITS)) + { + slot->eg_mode = ATTACK; + egout = (1 << EG_BITS) - 1; + UPDATE_EG(slot); + } + break; + + case FINISH: + egout = (1 << EG_BITS) - 1; + break; + + default: + egout = (1 << EG_BITS) - 1; + break; + } + + if (slot->patch->AM) + egout = EG2DB (egout + slot->tll) + lfo; + else + egout = EG2DB (egout + slot->tll); + + if (egout >= DB_MUTE) + egout = DB_MUTE - 1; + + slot->egout = egout | 3; +} + +/* CARRIOR */ +INLINE static e_int32 +calc_slot_car (OPLL_SLOT * slot, e_int32 fm) +{ + if (slot->egout >= (DB_MUTE - 1)) + { + slot->output[0] = 0; + } + else + { + slot->output[0] = DB2LIN_TABLE[slot->sintbl[(slot->pgout+wave2_8pi(fm))&(PG_WIDTH-1)] + slot->egout]; + } + + slot->output[1] = (slot->output[1] + slot->output[0]) >> 1; + return slot->output[1]; +} + +/* MODULATOR */ +INLINE static e_int32 +calc_slot_mod (OPLL_SLOT * slot) +{ + e_int32 fm; + + slot->output[1] = slot->output[0]; + + if (slot->egout >= (DB_MUTE - 1)) + { + slot->output[0] = 0; + } + else if (slot->patch->FB != 0) + { + fm = wave2_4pi (slot->feedback) >> (7 - slot->patch->FB); + slot->output[0] = DB2LIN_TABLE[slot->sintbl[(slot->pgout+fm)&(PG_WIDTH-1)] + slot->egout]; + } + else + { + slot->output[0] = DB2LIN_TABLE[slot->sintbl[slot->pgout] + slot->egout]; + } + + slot->feedback = (slot->output[1] + slot->output[0]) >> 1; + + return slot->feedback; + +} + +/* TOM */ +INLINE static e_int32 +calc_slot_tom (OPLL_SLOT * slot) +{ + if (slot->egout >= (DB_MUTE - 1)) + return 0; + + return DB2LIN_TABLE[slot->sintbl[slot->pgout] + slot->egout]; + +} + +/* SNARE */ +INLINE static e_int32 +calc_slot_snare (OPLL_SLOT * slot, e_uint32 noise) +{ + if(slot->egout>=(DB_MUTE-1)) + return 0; + + if(BIT(slot->pgout,7)) + return DB2LIN_TABLE[(noise?DB_POS(0.0):DB_POS(15.0))+slot->egout]; + else + return DB2LIN_TABLE[(noise?DB_NEG(0.0):DB_NEG(15.0))+slot->egout]; +} + +/* + TOP-CYM + */ +INLINE static e_int32 +calc_slot_cym (OPLL_SLOT * slot, e_uint32 pgout_hh) +{ + e_uint32 dbout; + + if (slot->egout >= (DB_MUTE - 1)) + return 0; + else if( + /* the same as fmopl.c */ + ((BIT(pgout_hh,PG_BITS-8)^BIT(pgout_hh,PG_BITS-1))|BIT(pgout_hh,PG_BITS-7)) ^ + /* different from fmopl.c */ + (BIT(slot->pgout,PG_BITS-7)&!BIT(slot->pgout,PG_BITS-5)) + ) + dbout = DB_NEG(3.0); + else + dbout = DB_POS(3.0); + + return DB2LIN_TABLE[dbout + slot->egout]; +} + +/* + HI-HAT +*/ +INLINE static e_int32 +calc_slot_hat (OPLL_SLOT *slot, e_int32 pgout_cym, e_uint32 noise) +{ + e_uint32 dbout; + + if (slot->egout >= (DB_MUTE - 1)) + return 0; + else if( + /* the same as fmopl.c */ + ((BIT(slot->pgout,PG_BITS-8)^BIT(slot->pgout,PG_BITS-1))|BIT(slot->pgout,PG_BITS-7)) ^ + /* different from fmopl.c */ + (BIT(pgout_cym,PG_BITS-7)&!BIT(pgout_cym,PG_BITS-5)) + ) + { + if(noise) + dbout = DB_NEG(12.0); + else + dbout = DB_NEG(24.0); + } + else + { + if(noise) + dbout = DB_POS(12.0); + else + dbout = DB_POS(24.0); + } + + return DB2LIN_TABLE[dbout + slot->egout]; +} + +static e_int16 +calc (OPLL * opll) +{ + e_int32 i; + + update_ampm (opll); + update_noise (opll); + + for (i = 0; i < 18; i++) + { + calc_phase(&opll->slot[i],opll->lfo_pm); + calc_envelope(&opll->slot[i],opll->lfo_am); + } + + e_uint32 channel_mask = opll->mask; + for (i = 0; i < 9; i++) { + if (CAR(opll,i)->eg_mode != FINISH) + channel_mask |= (1 << i); + } + + e_int32 mix = 0; + + /* CH6 */ + if (opll->patch_number[6] & 0x10) { + if (channel_mask & OPLL_MASK_CH (6)) { + mix += calc_slot_car (CAR(opll,6), calc_slot_mod(MOD(opll,6))); + channel_mask &= ~(1 << 6); + } + } + + /* CH7 */ + if (opll->patch_number[7] & 0x10) { + if (MOD(opll,7)->eg_mode != FINISH) + mix += calc_slot_hat (MOD(opll,7), CAR(opll,8)->pgout, opll->noise_seed&1); + if (channel_mask & OPLL_MASK_SD) { + mix -= calc_slot_snare (CAR(opll,7), opll->noise_seed&1); + channel_mask &= ~OPLL_MASK_SD; + } + } + + /* CH8 */ + if (opll->patch_number[8] & 0x10) { + if (MOD(opll,8)->eg_mode != FINISH) + mix += calc_slot_tom (MOD(opll,8)); + if (channel_mask & OPLL_MASK_CYM) { + mix -= calc_slot_cym (CAR(opll,8), MOD(opll,7)->pgout); + channel_mask &= ~OPLL_MASK_CYM; + } + } + + mix <<= 1; + + opll->current_mask = channel_mask; + for (i = 0; channel_mask; channel_mask >>= 1, ++i) { + if (channel_mask & 1) { + mix += calc_slot_car (CAR(opll,i), calc_slot_mod(MOD(opll,i))); + } + } + + return (e_int16) mix << 3; +} + +void +OPLL_set_internal_mute(OPLL * opll, e_uint32 mute) +{ + opll->internal_mute = mute; +} + +e_uint32 +OPLL_is_internal_muted(OPLL * opll) +{ + return opll->internal_mute; +} + +e_uint32 +check_mute_helper(OPLL * opll) +{ + for (int i = 0; i < 6; i++) { + /* if (ch[i].car.eg_mode != FINISH) return 0; */ + if (!(opll->current_mask & OPLL_MASK_CH (i)) && (CAR(opll,i)->eg_mode != FINISH)) return 0; + } + + if (!(opll->reg[0x0e] & 0x20)) { + for(int i = 6; i < 9; i++) { + /* if (ch[i].car.eg_mode != FINISH) return 0; */ + if (!(opll->current_mask & OPLL_MASK_CH (i)) && (CAR(opll,i)->eg_mode != FINISH)) return 0; + } + } else { + /* if (ch[6].car.eg_mode != FINISH) return false; + if (ch[7].mod.eg_mode != FINISH) return false; + if (ch[7].car.eg_mode != FINISH) return false; + if (ch[8].mod.eg_mode != FINISH) return false; + if (ch[8].car.eg_mode != FINISH) return false; */ + if (!(opll->current_mask & OPLL_MASK_CH (6)) && (CAR(opll,6)->eg_mode != FINISH)) return 0; + if (!(opll->current_mask & OPLL_MASK_CH (7)) && (MOD(opll,7)->eg_mode != FINISH)) return 0; + if (!(opll->current_mask & OPLL_MASK_CH (7)) && (CAR(opll,7)->eg_mode != FINISH)) return 0; + if (!(opll->current_mask & OPLL_MASK_CH (8)) && (MOD(opll,8)->eg_mode != FINISH)) return 0; + if (!(opll->current_mask & OPLL_MASK_CH (8)) && (CAR(opll,8)->eg_mode != FINISH)) return 0; + } + + return 1; /* nothing is playing, then mute */ +} + +void +check_mute(OPLL * opll) +{ + OPLL_set_internal_mute (opll, check_mute_helper (opll)); +} + +EMU2413_API e_int16 *OPLL_update_buffer(OPLL * opll, e_uint32 length) +{ + e_int16* buf = opll->buffer; + while (length--) { + *(buf++) = calc (opll); + } + check_mute (opll); + + return opll->buffer; +} + +#ifdef EMU2413_COMPACTION +e_int16 +OPLL_calc (OPLL * opll) +{ + return calc (opll); +} +#else +e_int16 +OPLL_calc (OPLL * opll) +{ + if (!opll->quality) + return calc (opll); + + while (opll->realstep > opll->oplltime) + { + opll->oplltime += opll->opllstep; + opll->prev = opll->next; + opll->next = calc (opll); + } + + opll->oplltime -= opll->realstep; + opll->out = (e_int16) (((double) opll->next * (opll->opllstep - opll->oplltime) + + (double) opll->prev * opll->oplltime) / opll->opllstep); + + return (e_int16) opll->out; +} +#endif + +e_uint32 +OPLL_setMask (OPLL * opll, e_uint32 mask) +{ + e_uint32 ret; + + if (opll) + { + ret = opll->mask; + opll->mask = mask; + return ret; + } + else + return 0; +} + +e_uint32 +OPLL_toggleMask (OPLL * opll, e_uint32 mask) +{ + e_uint32 ret; + + if (opll) + { + ret = opll->mask; + opll->mask ^= mask; + return ret; + } + else + return 0; +} + +/**************************************************** + + I/O Ctrl + +*****************************************************/ + +void +OPLL_writeReg (OPLL * opll, e_uint32 reg, e_uint32 data) +{ + e_int32 i, v, ch; + + data = data & 0xff; + reg = reg & 0x3f; + opll->reg[reg] = (e_uint8) data; + + switch (reg) + { + case 0x00: + opll->patch[0].AM = (data >> 7) & 1; + opll->patch[0].PM = (data >> 6) & 1; + opll->patch[0].EG = (data >> 5) & 1; + opll->patch[0].KR = (data >> 4) & 1; + opll->patch[0].ML = (data) & 15; + for (i = 0; i < 9; i++) + { + if (opll->patch_number[i] == 0) + { + UPDATE_PG (MOD(opll,i)); + UPDATE_RKS (MOD(opll,i)); + UPDATE_EG (MOD(opll,i)); + } + } + break; + + case 0x01: + opll->patch[1].AM = (data >> 7) & 1; + opll->patch[1].PM = (data >> 6) & 1; + opll->patch[1].EG = (data >> 5) & 1; + opll->patch[1].KR = (data >> 4) & 1; + opll->patch[1].ML = (data) & 15; + for (i = 0; i < 9; i++) + { + if (opll->patch_number[i] == 0) + { + UPDATE_PG (CAR(opll,i)); + UPDATE_RKS (CAR(opll,i)); + UPDATE_EG (CAR(opll,i)); + } + } + break; + + case 0x02: + opll->patch[0].KL = (data >> 6) & 3; + opll->patch[0].TL = (data) & 63; + for (i = 0; i < 9; i++) + { + if (opll->patch_number[i] == 0) + { + UPDATE_TLL(MOD(opll,i)); + } + } + break; + + case 0x03: + opll->patch[1].KL = (data >> 6) & 3; + opll->patch[1].WF = (data >> 4) & 1; + opll->patch[0].WF = (data >> 3) & 1; + opll->patch[0].FB = (data) & 7; + for (i = 0; i < 9; i++) + { + if (opll->patch_number[i] == 0) + { + UPDATE_WF(MOD(opll,i)); + UPDATE_WF(CAR(opll,i)); + } + } + break; + + case 0x04: + opll->patch[0].AR = (data >> 4) & 15; + opll->patch[0].DR = (data) & 15; + for (i = 0; i < 9; i++) + { + if (opll->patch_number[i] == 0) + { + UPDATE_EG (MOD(opll,i)); + } + } + break; + + case 0x05: + opll->patch[1].AR = (data >> 4) & 15; + opll->patch[1].DR = (data) & 15; + for (i = 0; i < 9; i++) + { + if (opll->patch_number[i] == 0) + { + UPDATE_EG(CAR(opll,i)); + } + } + break; + + case 0x06: + opll->patch[0].SL = (data >> 4) & 15; + opll->patch[0].RR = (data) & 15; + for (i = 0; i < 9; i++) + { + if (opll->patch_number[i] == 0) + { + UPDATE_EG (MOD(opll,i)); + } + } + break; + + case 0x07: + opll->patch[1].SL = (data >> 4) & 15; + opll->patch[1].RR = (data) & 15; + for (i = 0; i < 9; i++) + { + if (opll->patch_number[i] == 0) + { + UPDATE_EG (CAR(opll,i)); + } + } + break; + + case 0x0e: + update_rhythm_mode (opll); + if (data & 32) + { + if (data & 0x10) + keyOn_BD (opll); + else + keyOff_BD (opll); + if (data & 0x8) + keyOn_SD (opll); + else + keyOff_SD (opll); + if (data & 0x4) + keyOn_TOM (opll); + else + keyOff_TOM (opll); + if (data & 0x2) + keyOn_CYM (opll); + else + keyOff_CYM (opll); + if (data & 0x1) + keyOn_HH (opll); + else + keyOff_HH (opll); + } + update_key_status (opll); + + UPDATE_ALL (MOD(opll,6)); + UPDATE_ALL (CAR(opll,6)); + UPDATE_ALL (MOD(opll,7)); + UPDATE_ALL (CAR(opll,7)); + UPDATE_ALL (MOD(opll,8)); + UPDATE_ALL (CAR(opll,8)); + + break; + + case 0x0f: + break; + + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + ch = reg - 0x10; + setFnumber (opll, ch, data + ((opll->reg[0x20 + ch] & 1) << 8)); + UPDATE_ALL (MOD(opll,ch)); + UPDATE_ALL (CAR(opll,ch)); + break; + + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + ch = reg - 0x20; + setFnumber (opll, ch, ((data & 1) << 8) + opll->reg[0x10 + ch]); + setBlock (opll, ch, (data >> 1) & 7); + setSustine (opll, ch, (data >> 5) & 1); + if (data & 0x10) + keyOn (opll, ch); + else + keyOff (opll, ch); + UPDATE_ALL (MOD(opll,ch)); + UPDATE_ALL (CAR(opll,ch)); + update_key_status (opll); + update_rhythm_mode (opll); + break; + + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + i = (data >> 4) & 15; + v = data & 15; + if ((opll->reg[0x0e] & 32) && (reg >= 0x36)) + { + switch (reg) + { + case 0x37: + setSlotVolume (MOD(opll,7), i << 2); + break; + case 0x38: + setSlotVolume (MOD(opll,8), i << 2); + break; + default: + break; + } + } + else + { + setPatch (opll, reg - 0x30, i); + } + setVolume (opll, reg - 0x30, v << 2); + UPDATE_ALL (MOD(opll,reg - 0x30)); + UPDATE_ALL (CAR(opll,reg - 0x30)); + break; + + default: + break; + + } +} + +void +OPLL_writeIO (OPLL * opll, e_uint32 adr, e_uint32 val) +{ + if (adr & 1) + OPLL_writeReg (opll, opll->adr, val); + else + opll->adr = val; +} + +e_uint32 +OPLL_read(OPLL * opll, e_uint32 a) +{ + if( !(a&1) ) + { + /* status port */ + return opll->status; + } + return 0xff; +} + +#ifndef EMU2413_COMPACTION +/* STEREO MODE (OPT) */ +void +OPLL_set_pan (OPLL * opll, e_uint32 ch, e_uint32 pan) +{ + opll->pan[ch & 15] = pan & 3; +} + +static void +calc_stereo (OPLL * opll, e_int32 out[2]) +{ + e_int32 b[4] = { 0, 0, 0, 0 }; /* Ignore, Right, Left, Center */ + e_int32 r[4] = { 0, 0, 0, 0 }; /* Ignore, Right, Left, Center */ + e_int32 i; + + update_ampm (opll); + update_noise (opll); + + for(i=0;i<18;i++) + { + calc_phase(&opll->slot[i],opll->lfo_pm); + calc_envelope(&opll->slot[i],opll->lfo_am); + } + + for (i = 0; i < 6; i++) + if (!(opll->mask & OPLL_MASK_CH (i)) && (CAR(opll,i)->eg_mode != FINISH)) + b[opll->pan[i]] += calc_slot_car (CAR(opll,i), calc_slot_mod (MOD(opll,i))); + + + if (opll->patch_number[6] <= 15) + { + if (!(opll->mask & OPLL_MASK_CH (6)) && (CAR(opll,6)->eg_mode != FINISH)) + b[opll->pan[6]] += calc_slot_car (CAR(opll,6), calc_slot_mod (MOD(opll,6))); + } + else + { + if (!(opll->mask & OPLL_MASK_BD) && (CAR(opll,6)->eg_mode != FINISH)) + r[opll->pan[9]] += calc_slot_car (CAR(opll,6), calc_slot_mod (MOD(opll,6))); + } + + if (opll->patch_number[7] <= 15) + { + if (!(opll->mask & OPLL_MASK_CH (7)) && (CAR (opll,7)->eg_mode != FINISH)) + b[opll->pan[7]] += calc_slot_car (CAR (opll,7), calc_slot_mod (MOD (opll,7))); + } + else + { + if (!(opll->mask & OPLL_MASK_HH) && (MOD (opll,7)->eg_mode != FINISH)) + r[opll->pan[10]] += calc_slot_hat (MOD (opll,7), CAR(opll,8)->pgout, opll->noise_seed&1); + if (!(opll->mask & OPLL_MASK_SD) && (CAR (opll,7)->eg_mode != FINISH)) + r[opll->pan[11]] -= calc_slot_snare (CAR (opll,7), opll->noise_seed&1); + } + + if (opll->patch_number[8] <= 15) + { + if (!(opll->mask & OPLL_MASK_CH (8)) && (CAR (opll,8)->eg_mode != FINISH)) + b[opll->pan[8]] += calc_slot_car (CAR (opll,8), calc_slot_mod (MOD (opll,8))); + } + else + { + if (!(opll->mask & OPLL_MASK_TOM) && (MOD (opll,8)->eg_mode != FINISH)) + r[opll->pan[12]] += calc_slot_tom (MOD (opll,8)); + if (!(opll->mask & OPLL_MASK_CYM) && (CAR (opll,8)->eg_mode != FINISH)) + r[opll->pan[13]] -= calc_slot_cym (CAR (opll,8), MOD(opll,7)->pgout); + } + + out[1] = (b[1] + b[3] + ((r[1] + r[3]) << 1)) <<3; + out[0] = (b[2] + b[3] + ((r[2] + r[3]) << 1)) <<3; +} + +void +OPLL_calc_stereo (OPLL * opll, e_int32 out[2]) +{ + if (!opll->quality) + { + calc_stereo (opll, out); + return; + } + + while (opll->realstep > opll->oplltime) + { + opll->oplltime += opll->opllstep; + opll->sprev[0] = opll->snext[0]; + opll->sprev[1] = opll->snext[1]; + calc_stereo (opll, opll->snext); + } + + opll->oplltime -= opll->realstep; + out[0] = (e_int16) (((double) opll->snext[0] * (opll->opllstep - opll->oplltime) + + (double) opll->sprev[0] * opll->oplltime) / opll->opllstep); + out[1] = (e_int16) (((double) opll->snext[1] * (opll->opllstep - opll->oplltime) + + (double) opll->sprev[1] * opll->oplltime) / opll->opllstep); +} +#endif /* EMU2413_COMPACTION */ diff --git a/apps/codecs/libgme/emu2413.h b/apps/codecs/libgme/emu2413.h new file mode 100644 index 0000000000..9ee4513ff3 --- /dev/null +++ b/apps/codecs/libgme/emu2413.h @@ -0,0 +1,164 @@ +#ifndef _EMU2413_H_ +#define _EMU2413_H_ + +#include "blargg_common.h" +#include "emutypes.h" + +#ifdef EMU2413_DLL_EXPORTS + #define EMU2413_API __declspec(dllexport) +#elif defined(EMU2413_DLL_IMPORTS) + #define EMU2413_API __declspec(dllimport) +#else + #define EMU2413_API +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define AUDIO_MONO_BUFFER_SIZE 1024 + +#define PI 3.14159265358979323846 + +enum OPLL_TONE_ENUM {OPLL_2413_TONE=0, OPLL_VRC7_TONE=1, OPLL_281B_TONE=2} ; + +/* voice data */ +typedef struct __OPLL_PATCH { + e_uint32 TL,FB,EG,ML,AR,DR,SL,RR,KR,KL,AM,PM,WF ; +} OPLL_PATCH ; + +/* slot */ +typedef struct __OPLL_SLOT { + + OPLL_PATCH *patch; + + e_int32 type ; /* 0 : modulator 1 : carrier */ + + /* OUTPUT */ + e_int32 feedback ; + e_int32 output[2] ; /* Output value of slot */ + + /* for Phase Generator (PG) */ + e_uint16 *sintbl ; /* Wavetable */ + e_uint32 phase ; /* Phase */ + e_uint32 dphase ; /* Phase increment amount */ + e_uint32 pgout ; /* output */ + + /* for Envelope Generator (EG) */ + e_int32 fnum ; /* F-Number */ + e_int32 block ; /* Block */ + e_int32 volume ; /* Current volume */ + e_int32 sustine ; /* Sustine 1 = ON, 0 = OFF */ + e_uint32 tll ; /* Total Level + Key scale level*/ + e_uint32 rks ; /* Key scale offset (Rks) */ + e_int32 eg_mode ; /* Current state */ + e_uint32 eg_phase ; /* Phase */ + e_uint32 eg_dphase ; /* Phase increment amount */ + e_uint32 egout ; /* output */ + +} OPLL_SLOT ; + +/* Mask */ +#define OPLL_MASK_CH(x) (1<<(x)) +#define OPLL_MASK_HH (1<<(9)) +#define OPLL_MASK_CYM (1<<(10)) +#define OPLL_MASK_TOM (1<<(11)) +#define OPLL_MASK_SD (1<<(12)) +#define OPLL_MASK_BD (1<<(13)) +#define OPLL_MASK_RHYTHM ( OPLL_MASK_HH | OPLL_MASK_CYM | OPLL_MASK_TOM | OPLL_MASK_SD | OPLL_MASK_BD ) + +/* opll */ +typedef struct __OPLL { + + e_uint32 adr ; + e_int32 out ; + +#ifndef EMU2413_COMPACTION + e_uint32 realstep ; + e_uint32 oplltime ; + e_uint32 opllstep ; + e_int32 prev, next ; + e_int32 sprev[2],snext[2]; + e_uint32 pan[16]; +#endif + + /* Register */ + e_uint8 reg[0x40] ; + e_int32 slot_on_flag[18] ; + + /* Pitch Modulator */ + e_uint32 pm_phase ; + e_int32 lfo_pm ; + + /* Amp Modulator */ + e_int32 am_phase ; + e_int32 lfo_am ; + + e_uint32 quality; + + /* Noise Generator */ + e_uint32 noise_seed ; + + /* Channel Data */ + e_int32 patch_number[9]; + e_int32 key_status[9] ; + + /* Slot */ + OPLL_SLOT slot[18] ; + + /* Voice Data */ + OPLL_PATCH patch[19*2] ; + e_int32 patch_update[2] ; /* flag for check patch update */ + + e_uint32 mask ; + e_uint32 current_mask; + e_uint32 status; + + e_uint32 internal_mute; + e_int16 buffer[AUDIO_MONO_BUFFER_SIZE]; +} OPLL ; + +/* Create Object */ +EMU2413_API void OPLL_new(OPLL *, e_uint32 clk, e_uint32 rate) ; +EMU2413_API void OPLL_delete(OPLL *) ; + +/* Setup */ +EMU2413_API void OPLL_reset(OPLL *) ; +EMU2413_API void OPLL_reset_patch(OPLL *, e_int32) ; +EMU2413_API void OPLL_set_rate(OPLL *opll, e_uint32 r) ; +EMU2413_API void OPLL_set_quality(OPLL *opll, e_uint32 q) ; +EMU2413_API void OPLL_set_pan(OPLL *, e_uint32 ch, e_uint32 pan); +EMU2413_API void OPLL_set_internal_mute(OPLL *, e_uint32 mute); +EMU2413_API e_uint32 OPLL_is_internal_muted(OPLL *); + +/* Port/Register access */ +EMU2413_API void OPLL_writeIO(OPLL *, e_uint32 reg, e_uint32 val); ICODE_ATTR +EMU2413_API void OPLL_writeReg(OPLL *, e_uint32 reg, e_uint32 val); ICODE_ATTR +EMU2413_API e_uint32 OPLL_read(OPLL *, e_uint32 port); + +/* Synthsize */ +EMU2413_API e_int16 OPLL_calc(OPLL *) ; ICODE_ATTR +EMU2413_API void OPLL_calc_stereo(OPLL *, e_int32 out[2]) ; ICODE_ATTR +EMU2413_API e_int16 *OPLL_update_buffer(OPLL *, e_uint32 length) ; ICODE_ATTR + +/* Misc */ +EMU2413_API void OPLL_setPatch(OPLL *, const e_uint8 *dump) ; +EMU2413_API void OPLL_copyPatch(OPLL *, e_int32, OPLL_PATCH *) ; +EMU2413_API void OPLL_forceRefresh(OPLL *) ; +/* Utility */ +EMU2413_API void OPLL_dump2patch(const e_uint8 *dump, OPLL_PATCH *patch) ; +EMU2413_API void OPLL_patch2dump(const OPLL_PATCH *patch, e_uint8 *dump) ; +EMU2413_API void OPLL_getDefaultPatch(e_int32 type, e_int32 num, OPLL_PATCH *) ; + +/* Channel Mask */ +EMU2413_API e_uint32 OPLL_setMask(OPLL *, e_uint32 mask) ; +EMU2413_API e_uint32 OPLL_toggleMask(OPLL *, e_uint32 mask) ; + +#define dump2patch OPLL_dump2patch + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/apps/codecs/libgme/emu8950.c b/apps/codecs/libgme/emu8950.c new file mode 100644 index 0000000000..2198239004 --- /dev/null +++ b/apps/codecs/libgme/emu8950.c @@ -0,0 +1,1198 @@ +/* + * This file is based on: + * Y8950.cc -- Y8950 emulator from the openMSX team + * ported to c by gama + * + * The openMSX version is based on: + * emu8950.c -- Y8950 emulator written by Mitsutaka Okazaki 2001 + * heavily rewritten to fit openMSX structure + */ + +#include +#include "emu8950.h" + +#ifdef _MSC_VER +#pragma warning( disable : 4355 ) +#endif + +#if !defined(ROCKBOX) + #define EMU8950_CALCUL_TABLES +#else + #include "opltables.h" +#endif + +// dB to Liner table +static short dB2LinTab[(2*DB_MUTE)*2]; +// Dynamic range +static unsigned int dphaseNoiseTable[1024][8]; +// LFO Table +int pmtable[2][PM_PG_WIDTH]; +int amtable[2][AM_PG_WIDTH]; + +/** WaveTable for each envelope amp. */ +static int sintable[PG_WIDTH]; + /** Phase incr table for Attack. */ +static unsigned int dphaseARTable[16][16]; +/** Phase incr table for Decay and Release. */ +static unsigned int dphaseDRTable[16][16]; +/** KSL + TL Table. */ +static int tllTable[16][8][1<> b; +} +// Leave the lower b bits +inline static int LOWBITS(int c, int b) +{ + return c & ((1<> 1; + #else + AR_ADJUST_TABLE[i] = ar_adjust_coeff[i-1]; + #endif +} + +// Table for dB(0 -- (1<> (21 - DP_BITS)), sampleRate, clockRate); +} +#endif + +static void makeTllTable(void) +{ + #define dB2(x) (int)((x)*2) + static int kltable[16] = { + dB2( 0.000),dB2( 9.000),dB2(12.000),dB2(13.875), + dB2(15.000),dB2(16.125),dB2(16.875),dB2(17.625), + dB2(18.000),dB2(18.750),dB2(19.125),dB2(19.500), + dB2(19.875),dB2(20.250),dB2(20.625),dB2(21.000) + }; + + int fnum, block, TL, KL; + for (fnum=0; fnum<16; fnum++) + for (block=0; block<8; block++) + for (TL=0; TL<64; TL++) + for (KL=0; KL<4; KL++) { + if (KL==0) { + tllTable[fnum][block][TL][KL] = ALIGN(TL, TL_STEP, EG_STEP); + } else { + int tmp = kltable[fnum] - dB2(3.000) * (7 - block); + if (tmp <= 0) + tllTable[fnum][block][TL][KL] = ALIGN(TL, TL_STEP, EG_STEP); + else + tllTable[fnum][block][TL][KL] = (int)((tmp>>(3-KL))/EG_STEP) + ALIGN(TL, TL_STEP, EG_STEP); + } + } +} + + +// Rate Table for Attack +static void makeDphaseARTable(int sampleRate, int clockRate) +{ + int AR, Rks; + for (AR=0; AR<16; AR++) + for (Rks=0; Rks<16; Rks++) { + int RM = AR + (Rks>>2); + int RL = Rks&3; + if (RM>15) RM=15; + switch (AR) { + case 0: + dphaseARTable[AR][Rks] = 0; + break; + case 15: + dphaseARTable[AR][Rks] = EG_DP_WIDTH; + break; + default: + dphaseARTable[AR][Rks] = rate_adjust((3*(RL+4) << (RM+1)), sampleRate, clockRate); + break; + } + } +} + +// Rate Table for Decay +static void makeDphaseDRTable(int sampleRate, int clockRate) +{ + int DR, Rks; + for (DR=0; DR<16; DR++) + for (Rks=0; Rks<16; Rks++) { + int RM = DR + (Rks>>2); + int RL = Rks&3; + if (RM>15) RM=15; + switch (DR) { + case 0: + dphaseDRTable[DR][Rks] = 0; + break; + default: + dphaseDRTable[DR][Rks] = rate_adjust((RL+4) << (RM-1), sampleRate, clockRate); + break; + } + } +} + +static void makeRksTable(void) +{ + int fnum9, block, KR; + for (fnum9=0; fnum9<2; fnum9++) + for (block=0; block<8; block++) + for (KR=0; KR<2; KR++) { + rksTable[fnum9][block][KR] = (KR != 0) ? + (block<<1) + fnum9: + block>>1; + } +} + +//**********************************************************// +// // +// Patch // +// // +//**********************************************************// + + +void patchReset(struct Patch* patch) +{ + patch->AM = patch->PM = patch->EG = false; + patch->KR = patch->ML = patch->KL = patch->TL = + patch->FB = patch->AR = patch->DR = patch->SL = patch->RR = 0; +} + + +//**********************************************************// +// // +// Slot // +// // +//**********************************************************// + + +static inline void slotUpdatePG(struct Slot* slot) +{ +#if defined(ROCKBOX) + static const int mltable[16] = { + 1,1*2,2*2,3*2,4*2,5*2,6*2,7*2,8*2,9*2,10*2,10*2,12*2,12*2,15*2,15*2 + }; + + slot->dphase = ((slot->fnum * mltable[slot->patch.ML]) << slot->block) >> (21 - DP_BITS); +#else + slot->dphase = dphaseTable[slot->fnum][slot->block][slot->patch.ML]; +#endif +} + +static inline void slotUpdateTLL(struct Slot* slot) +{ + slot->tll = tllTable[slot->fnum>>6][slot->block][slot->patch.TL][slot->patch.KL]; +} + +static inline void slotUpdateRKS(struct Slot* slot) +{ + slot->rks = rksTable[slot->fnum>>9][slot->block][slot->patch.KR]; +} + +static inline void slotUpdateEG(struct Slot* slot) +{ + switch (slot->eg_mode) { + case ATTACK: + slot->eg_dphase = dphaseARTable[slot->patch.AR][slot->rks]; + break; + case DECAY: + slot->eg_dphase = dphaseDRTable[slot->patch.DR][slot->rks]; + break; + case SUSTINE: + slot->eg_dphase = dphaseDRTable[slot->patch.RR][slot->rks]; + break; + case RELEASE: + slot->eg_dphase = slot->patch.EG ? + dphaseDRTable[slot->patch.RR][slot->rks]: + dphaseDRTable[7] [slot->rks]; + break; + case SUSHOLD: + case FINISH: + slot->eg_dphase = 0; + break; + } +} + +static inline void slotUpdateAll(struct Slot* slot) +{ + slotUpdatePG(slot); + slotUpdateTLL(slot); + slotUpdateRKS(slot); + slotUpdateEG(slot); // EG should be last +} + +void slotReset(struct Slot* slot) +{ + slot->phase = 0; + slot->dphase = 0; + slot->output[0] = 0; + slot->output[1] = 0; + slot->feedback = 0; + slot->eg_mode = FINISH; + slot->eg_phase = EG_DP_WIDTH; + slot->eg_dphase = 0; + slot->rks = 0; + slot->tll = 0; + slot->fnum = 0; + slot->block = 0; + slot->pgout = 0; + slot->egout = 0; + slot->slotStatus = false; + patchReset(&slot->patch); + slotUpdateAll(slot); +} + +// Slot key on +static inline void slotOn(struct Slot* slot) +{ + if (!slot->slotStatus) { + slot->slotStatus = true; + slot->eg_mode = ATTACK; + slot->phase = 0; + slot->eg_phase = 0; + } +} + +// Slot key off +static inline void slotOff(struct Slot* slot) +{ + if (slot->slotStatus) { + slot->slotStatus = false; + if (slot->eg_mode == ATTACK) + slot->eg_phase = EXPAND_BITS(AR_ADJUST_TABLE[HIGHBITS(slot->eg_phase, EG_DP_BITS-EG_BITS)], EG_BITS, EG_DP_BITS); + slot->eg_mode = RELEASE; + } +} + + +//**********************************************************// +// // +// OPLChannel // +// // +//**********************************************************// + + +void channelReset(struct OPLChannel* ch) +{ + slotReset(&ch->mod); + slotReset(&ch->car); + ch->alg = false; +} + +// Set F-Number ( fnum : 10bit ) +static void channelSetFnumber(struct OPLChannel* ch, int fnum) +{ + ch->car.fnum = fnum; + ch->mod.fnum = fnum; +} + +// Set Block data (block : 3bit ) +static void channelSetBlock(struct OPLChannel* ch, int block) +{ + ch->car.block = block; + ch->mod.block = block; +} + +// OPLChannel key on +static void keyOn(struct OPLChannel* ch) +{ + slotOn(&ch->mod); + slotOn(&ch->car); +} + +// OPLChannel key off +static void keyOff(struct OPLChannel* ch) +{ + slotOff(&ch->mod); + slotOff(&ch->car); +} + + +//**********************************************************// +// // +// Y8950 // +// // +//**********************************************************// + +void OPL_init(struct Y8950* this, byte* ramBank, int sampleRam) +{ + this->clockRate = CLK_FREQ; + + ADPCM_init(&this->adpcm, this, ramBank, sampleRam); + + makePmTable(); + makeAmTable(); + + makeAdjustTable(); + makeDB2LinTable(); + makeTllTable(); + makeRksTable(); + makeSinTable(); + + int i; + for (i=0; i<9; i++) { + // TODO cleanup + this->slot[i*2+0] = &(this->ch[i].mod); + this->slot[i*2+1] = &(this->ch[i].car); + this->ch[i].mod.plfo_am = &this->lfo_am; + this->ch[i].mod.plfo_pm = &this->lfo_pm; + this->ch[i].car.plfo_am = &this->lfo_am; + this->ch[i].car.plfo_pm = &this->lfo_pm; + } + + OPL_reset(this); +} + +void OPL_setSampleRate(struct Y8950* this, int sampleRate, int clockRate) +{ + this->clockRate = clockRate; + ADPCM_setSampleRate(&this->adpcm, sampleRate, clockRate); + +#if !defined(ROCKBOX) + makeDphaseTable(sampleRate, clockRate); +#endif + makeDphaseARTable(sampleRate, clockRate); + makeDphaseDRTable(sampleRate, clockRate); + makeDphaseNoiseTable(sampleRate, clockRate); + this->pm_dphase = rate_adjust(PM_SPEED * PM_DP_WIDTH / (clockRate/72), sampleRate, clockRate); + this->am_dphase = rate_adjust(AM_SPEED * AM_DP_WIDTH / (clockRate/72), sampleRate, clockRate); +} + +// Reset whole of opl except patch datas. +void OPL_reset(struct Y8950* this) +{ + int i; + for (i=0; i<9; i++) + channelReset(&this->ch[i]); + this->output[0] = 0; + this->output[1] = 0; + + + this->dacSampleVolume = 0; + this->dacOldSampleVolume = 0; + this->dacSampleVolumeSum = 0; + this->dacCtrlVolume = 0; + this->dacDaVolume = 0; + this->dacEnabled = 0; + + this->rythm_mode = false; + this->am_mode = 0; + this->pm_mode = 0; + this->pm_phase = 0; + this->am_phase = 0; + this->noise_seed = 0xffff; + this->noiseA = 0; + this->noiseB = 0; + this->noiseA_phase = 0; + this->noiseB_phase = 0; + this->noiseA_dphase = 0; + this->noiseB_dphase = 0; + + for (i = 0; i < 0x100; ++i) + this->reg[i] = 0x00; + + this->reg[0x04] = 0x18; + this->reg[0x19] = 0x0F; // fixes 'Thunderbirds are Go' + this->status = 0x00; + this->statusMask = 0; + /* irq.reset(); */ + + ADPCM_reset(&this->adpcm); + OPL_setInternalMute(this, true); // muted +} + + +// Drum key on +static inline void keyOn_BD(struct Y8950* this) { keyOn(&this->ch[6]); } +static inline void keyOn_HH(struct Y8950* this) { slotOn(&this->ch[7].mod); } +static inline void keyOn_SD(struct Y8950* this) { slotOn(&this->ch[7].car); } +static inline void keyOn_TOM(struct Y8950* this) { slotOn(&this->ch[8].mod); } +static inline void keyOn_CYM(struct Y8950* this) { slotOn(&this->ch[8].car); } + +// Drum key off +static inline void keyOff_BD(struct Y8950* this) { keyOff(&this->ch[6]); } +static inline void keyOff_HH(struct Y8950* this) { slotOff(&this->ch[7].mod); } +static inline void keyOff_SD(struct Y8950* this) { slotOff(&this->ch[7].car); } +static inline void keyOff_TOM(struct Y8950* this){ slotOff(&this->ch[8].mod); } +static inline void keyOff_CYM(struct Y8950* this){ slotOff(&this->ch[8].car); } + +// Change Rhythm Mode +inline void setRythmMode(struct Y8950* this, int data) +{ + bool newMode = (data & 32) != 0; + if (this->rythm_mode != newMode) { + this->rythm_mode = newMode; + if (!this->rythm_mode) { + // ON->OFF + this->ch[6].mod.eg_mode = FINISH; // BD1 + this->ch[6].mod.slotStatus = false; + this->ch[6].car.eg_mode = FINISH; // BD2 + this->ch[6].car.slotStatus = false; + this->ch[7].mod.eg_mode = FINISH; // HH + this->ch[7].mod.slotStatus = false; + this->ch[7].car.eg_mode = FINISH; // SD + this->ch[7].car.slotStatus = false; + this->ch[8].mod.eg_mode = FINISH; // TOM + this->ch[8].mod.slotStatus = false; + this->ch[8].car.eg_mode = FINISH; // CYM + this->ch[8].car.slotStatus = false; + } + } +} + +//********************************************************// +// // +// Generate wave data // +// // +//********************************************************// + +// Convert Amp(0 to EG_HEIGHT) to Phase(0 to 4PI). +inline static int wave2_4pi(int e) +{ + int shift = SLOT_AMP_BITS - PG_BITS - 1; + if (shift > 0) + return e >> shift; + else + return e << -shift; +} + +// Convert Amp(0 to EG_HEIGHT) to Phase(0 to 8PI). +inline static int wave2_8pi(int e) +{ + int shift = SLOT_AMP_BITS - PG_BITS - 2; + if (shift > 0) + return e >> shift; + else + return e << -shift; +} + +static inline void update_noise(struct Y8950* this) +{ + if (this->noise_seed & 1) + this->noise_seed ^= 0x24000; + this->noise_seed >>= 1; + this->whitenoise = this->noise_seed&1 ? DB_POS(6) : DB_NEG(6); + + this->noiseA_phase += this->noiseA_dphase; + this->noiseB_phase += this->noiseB_dphase; + + this->noiseA_phase &= (0x40<<11) - 1; + if ((this->noiseA_phase>>11)==0x3f) + this->noiseA_phase = 0; + this->noiseA = this->noiseA_phase&(0x03<<11)?DB_POS(6):DB_NEG(6); + + this->noiseB_phase &= (0x10<<11) - 1; + this->noiseB = this->noiseB_phase&(0x0A<<11)?DB_POS(6):DB_NEG(6); +} + +static inline void update_ampm(struct Y8950* this) +{ + this->pm_phase = (this->pm_phase + this->pm_dphase)&(PM_DP_WIDTH - 1); + this->am_phase = (this->am_phase + this->am_dphase)&(AM_DP_WIDTH - 1); + this->lfo_am = amtable[this->am_mode][HIGHBITS(this->am_phase, AM_DP_BITS - AM_PG_BITS)]; + this->lfo_pm = pmtable[this->pm_mode][HIGHBITS(this->pm_phase, PM_DP_BITS - PM_PG_BITS)]; +} + +static inline void calc_phase(struct Slot* slot) +{ + if (slot->patch.PM) + slot->phase += (slot->dphase * (*slot->plfo_pm)) >> PM_AMP_BITS; + else + slot->phase += slot->dphase; + slot->phase &= (DP_WIDTH - 1); + slot->pgout = HIGHBITS(slot->phase, DP_BASE_BITS); +} + +static inline void calc_envelope(struct Slot* slot) +{ + #define S2E(x) (ALIGN((unsigned int)(x/SL_STEP),SL_STEP,EG_STEP)<<(EG_DP_BITS-EG_BITS)) + static unsigned int SL[16] = { + S2E( 0), S2E( 3), S2E( 6), S2E( 9), S2E(12), S2E(15), S2E(18), S2E(21), + S2E(24), S2E(27), S2E(30), S2E(33), S2E(36), S2E(39), S2E(42), S2E(93) + }; + + switch (slot->eg_mode) { + case ATTACK: + slot->eg_phase += slot->eg_dphase; + if (EG_DP_WIDTH & slot->eg_phase) { + slot->egout = 0; + slot->eg_phase= 0; + slot->eg_mode = DECAY; + slotUpdateEG(slot); + } else { + slot->egout = AR_ADJUST_TABLE[HIGHBITS(slot->eg_phase, EG_DP_BITS - EG_BITS)]; + } + break; + + case DECAY: + slot->eg_phase += slot->eg_dphase; + slot->egout = HIGHBITS(slot->eg_phase, EG_DP_BITS - EG_BITS); + if (slot->eg_phase >= SL[slot->patch.SL]) { + if (slot->patch.EG) { + slot->eg_phase = SL[slot->patch.SL]; + slot->eg_mode = SUSHOLD; + slotUpdateEG(slot); + } else { + slot->eg_phase = SL[slot->patch.SL]; + slot->eg_mode = SUSTINE; + slotUpdateEG(slot); + } + slot->egout = HIGHBITS(slot->eg_phase, EG_DP_BITS - EG_BITS); + } + break; + + case SUSHOLD: + slot->egout = HIGHBITS(slot->eg_phase, EG_DP_BITS - EG_BITS); + if (!slot->patch.EG) { + slot->eg_mode = SUSTINE; + slotUpdateEG(slot); + } + break; + + case SUSTINE: + case RELEASE: + slot->eg_phase += slot->eg_dphase; + slot->egout = HIGHBITS(slot->eg_phase, EG_DP_BITS - EG_BITS); + if (slot->egout >= (1<eg_mode = FINISH; + slot->egout = (1<egout = (1<patch.AM) + slot->egout = ALIGN(slot->egout+slot->tll,EG_STEP,DB_STEP) + (*slot->plfo_am); + else + slot->egout = ALIGN(slot->egout+slot->tll,EG_STEP,DB_STEP); + if (slot->egout >= DB_MUTE) + slot->egout = DB_MUTE-1; +} + +inline static int calc_slot_car(struct Slot* slot, int fm) +{ + calc_envelope(slot); + calc_phase(slot); + if (slot->egout>=(DB_MUTE-1)) + return 0; + return dB2LinTab[sintable[(slot->pgout+wave2_8pi(fm))&(PG_WIDTH-1)] + slot->egout]; +} + +inline static int calc_slot_mod(struct Slot* slot) +{ + slot->output[1] = slot->output[0]; + calc_envelope(slot); + calc_phase(slot); + + if (slot->egout>=(DB_MUTE-1)) { + slot->output[0] = 0; + } else if (slot->patch.FB!=0) { + int fm = wave2_4pi(slot->feedback) >> (7-slot->patch.FB); + slot->output[0] = dB2LinTab[sintable[(slot->pgout+fm)&(PG_WIDTH-1)] + slot->egout]; + } else + slot->output[0] = dB2LinTab[sintable[slot->pgout] + slot->egout]; + + slot->feedback = (slot->output[1] + slot->output[0])>>1; + return slot->feedback; +} + +// TOM +inline static int calc_slot_tom(struct Slot* slot) +{ + calc_envelope(slot); + calc_phase(slot); + if (slot->egout>=(DB_MUTE-1)) + return 0; + return dB2LinTab[sintable[slot->pgout] + slot->egout]; +} + +// SNARE +inline static int calc_slot_snare(struct Slot* slot, int whitenoise) +{ + calc_envelope(slot); + calc_phase(slot); + if (slot->egout>=(DB_MUTE-1)) + return 0; + if (slot->pgout & (1<<(PG_BITS-1))) { + return (dB2LinTab[slot->egout] + dB2LinTab[slot->egout+whitenoise]) >> 1; + } else { + return (dB2LinTab[2*DB_MUTE + slot->egout] + dB2LinTab[slot->egout+whitenoise]) >> 1; + } +} + +// TOP-CYM +inline static int calc_slot_cym(struct Slot* slot, int a, int b) +{ + calc_envelope(slot); + if (slot->egout>=(DB_MUTE-1)) { + return 0; + } else { + return (dB2LinTab[slot->egout+a] + dB2LinTab[slot->egout+b]) >> 1; + } +} + +// HI-HAT +inline static int calc_slot_hat(struct Slot* slot, int a, int b, int whitenoise) +{ + calc_envelope(slot); + if (slot->egout>=(DB_MUTE-1)) { + return 0; + } else { + return (dB2LinTab[slot->egout+whitenoise] + dB2LinTab[slot->egout+a] + dB2LinTab[slot->egout+b]) >>2; + } +} + + +static inline int calcSample(struct Y8950* this, int channelMask) +{ + // while muted update_ampm() and update_noise() aren't called, probably ok + update_ampm(this); + update_noise(this); + + int mix = 0; + + if (this->rythm_mode) { + // TODO wasn't in original source either + calc_phase(&this->ch[7].mod); + calc_phase(&this->ch[8].car); + + if (channelMask & (1 << 6)) + mix += calc_slot_car(&this->ch[6].car, calc_slot_mod(&this->ch[6].mod)); + if (this->ch[7].mod.eg_mode != FINISH) + mix += calc_slot_hat(&this->ch[7].mod, this->noiseA, this->noiseB, this->whitenoise); + if (channelMask & (1 << 7)) + mix += calc_slot_snare(&this->ch[7].car, this->whitenoise); + if (this->ch[8].mod.eg_mode != FINISH) + mix += calc_slot_tom(&this->ch[8].mod); + if (channelMask & (1 << 8)) + mix += calc_slot_cym(&this->ch[8].car, this->noiseA, this->noiseB); + + channelMask &= (1<< 6) - 1; + mix *= 2; + } + struct OPLChannel *cp; + for (cp = this->ch; channelMask; channelMask >>=1, cp++) { + if (channelMask & 1) { + if (cp->alg) + mix += calc_slot_car(&cp->car, 0) + + calc_slot_mod(&cp->mod); + else + mix += calc_slot_car(&cp->car, + calc_slot_mod(&cp->mod)); + } + } + + mix += ADPCM_calcSample(&this->adpcm); + + return (mix*this->maxVolume) >> (DB2LIN_AMP_BITS - 1); +} + +bool checkMuteHelper(struct Y8950* this) +{ + int i; + struct OPLChannel *ch = this->ch; + for (i = 0; i < 6; i++) { + if (ch[i].car.eg_mode != FINISH) return false; + } + if (!this->rythm_mode) { + for(i = 6; i < 9; i++) { + if (ch[i].car.eg_mode != FINISH) return false; + } + } else { + if (ch[6].car.eg_mode != FINISH) return false; + if (ch[7].mod.eg_mode != FINISH) return false; + if (ch[7].car.eg_mode != FINISH) return false; + if (ch[8].mod.eg_mode != FINISH) return false; + if (ch[8].car.eg_mode != FINISH) return false; + } + + return ADPCM_muted(&this->adpcm); +} + +void checkMute(struct Y8950* this) +{ + bool mute = checkMuteHelper(this); + //PRT_DEBUG("Y8950: muted " << mute); + OPL_setInternalMute(this, mute); +} + +int* OPL_updateBuffer(struct Y8950* this, int length) +{ + //PRT_DEBUG("Y8950: update buffer"); + + if (OPL_isInternalMuted(this) && !this->dacEnabled) { + return 0; + } + + this->dacCtrlVolume = this->dacSampleVolume - this->dacOldSampleVolume + 0x3fe7 * this->dacCtrlVolume / 0x4000; + this->dacOldSampleVolume = this->dacSampleVolume; + + int channelMask = 0, i; + struct OPLChannel *ch = this->ch; + for (i = 9; i--; ) { + channelMask <<= 1; + if (ch[i].car.eg_mode != FINISH) channelMask |= 1; + } + + int* buf = this->buffer; + while (length--) { + int sample = calcSample(this, channelMask); + + this->dacCtrlVolume = 0x3fe7 * this->dacCtrlVolume / 0x4000; + this->dacDaVolume += 2 * (this->dacCtrlVolume - this->dacDaVolume) / 3; + sample += 48 * this->dacDaVolume; + *(buf++) = sample; + } + + this->dacEnabled = this->dacDaVolume; + + checkMute(this); + return this->buffer; +} + +void OPL_setInternalVolume(struct Y8950* this, short newVolume) +{ + this->maxVolume = newVolume; +} + +//**************************************************// +// // +// I/O Ctrl // +// // +//**************************************************// + +void OPL_writeReg(struct Y8950* this, byte rg, byte data) +{ + //PRT_DEBUG("Y8950 write " << (int)rg << " " << (int)data); + int stbl[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 + }; + + //TODO only for registers that influence sound + //TODO also ADPCM + + switch (rg & 0xe0) { + case 0x00: { + switch (rg) { + case 0x01: // TEST + // TODO + // Y8950 MSX-AUDIO Test register $01 (write only) + // + // Bit Description + // + // 7 Reset LFOs - seems to force the LFOs to their initial values (eg. + // maximum amplitude, zero phase deviation) + // + // 6 something to do with ADPCM - bit 0 of the status register is + // affected by setting this bit (PCM BSY) + // + // 5 No effect? - Waveform select enable in YM3812 OPL2 so seems + // reasonable that this bit wouldn't have been used in OPL + // + // 4 No effect? + // + // 3 Faster LFOs - increases the frequencies of the LFOs and (maybe) + // the timers (cf. YM2151 test register) + // + // 2 Reset phase generators - No phase generator output, but envelope + // generators still work (can hear a transient when they are gated) + // + // 1 No effect? + // + // 0 Reset envelopes - Envelope generator outputs forced to maximum, + // so all enabled voices sound at maximum + this->reg[rg] = data; + break; + + case 0x02: // TIMER1 (reso. 80us) + this->reg[rg] = data; + break; + + case 0x03: // TIMER2 (reso. 320us) + this->reg[rg] = data; + break; + + case 0x04: // FLAG CONTROL + if (data & R04_IRQ_RESET) { + OPL_resetStatus(this, 0x78); // reset all flags + } else { + OPL_changeStatusMask(this, (~data) & 0x78); + this->reg[rg] = data; + } + break; + + case 0x06: // (KEYBOARD OUT) + this->reg[rg] = data; + break; + + case 0x07: // START/REC/MEM DATA/REPEAT/SP-OFF/-/-/RESET + case 0x08: // CSM/KEY BOARD SPLIT/-/-/SAMPLE/DA AD/64K/ROM + case 0x09: // START ADDRESS (L) + case 0x0A: // START ADDRESS (H) + case 0x0B: // STOP ADDRESS (L) + case 0x0C: // STOP ADDRESS (H) + case 0x0D: // PRESCALE (L) + case 0x0E: // PRESCALE (H) + case 0x0F: // ADPCM-DATA + case 0x10: // DELTA-N (L) + case 0x11: // DELTA-N (H) + case 0x12: // ENVELOP CONTROL + case 0x1A: // PCM-DATA + this->reg[rg] = data; + ADPCM_writeReg(&this->adpcm, rg, data); + break; + + case 0x15: // DAC-DATA (bit9-2) + this->reg[rg] = data; + if (this->reg[0x08] & 0x04) { + static int damp[] = { 256, 279, 304, 332, 362, 395, 431, 470 }; + int sample = (short)(256 * this->reg[0x15] + this->reg[0x16]) * 128 / damp[this->reg[0x17]]; + this->dacSampleVolume = sample; + this->dacEnabled = 1; + } + break; + case 0x16: // (bit1-0) + this->reg[rg] = data & 0xC0; + break; + case 0x17: // (exponent) + this->reg[rg] = data & 0x07; + break; + + case 0x18: // I/O-CONTROL (bit3-0) + // TODO + // 0 -> input + // 1 -> output + this->reg[rg] = data; + break; + + case 0x19: // I/O-DATA (bit3-0) + // TODO + this->reg[rg] = data; + break; + } + + break; + } + case 0x20: { + int s = stbl[rg&0x1f]; + if (s >= 0) { + this->slot[s]->patch.AM = (data>>7)&1; + this->slot[s]->patch.PM = (data>>6)&1; + this->slot[s]->patch.EG = (data>>5)&1; + this->slot[s]->patch.KR = (data>>4)&1; + this->slot[s]->patch.ML = (data)&15; + slotUpdateAll(this->slot[s]); + } + this->reg[rg] = data; + break; + } + case 0x40: { + int s = stbl[rg&0x1f]; + if (s >= 0) { + this->slot[s]->patch.KL = (data>>6)&3; + this->slot[s]->patch.TL = (data)&63; + slotUpdateAll(this->slot[s]); + } + this->reg[rg] = data; + break; + } + case 0x60: { + int s = stbl[rg&0x1f]; + if (s >= 0) { + this->slot[s]->patch.AR = (data>>4)&15; + this->slot[s]->patch.DR = (data)&15; + slotUpdateEG(this->slot[s]); + } + this->reg[rg] = data; + break; + } + case 0x80: { + int s = stbl[rg&0x1f]; + if (s >= 0) { + this->slot[s]->patch.SL = (data>>4)&15; + this->slot[s]->patch.RR = (data)&15; + slotUpdateEG(this->slot[s]); + } + this->reg[rg] = data; + break; + } + case 0xa0: { + if (rg==0xbd) { + this->am_mode = (data>>7)&1; + this->pm_mode = (data>>6)&1; + + setRythmMode(this, data); + if (this->rythm_mode) { + if (data&0x10) keyOn_BD(this); else keyOff_BD(this); + if (data&0x08) keyOn_SD(this); else keyOff_SD(this); + if (data&0x04) keyOn_TOM(this); else keyOff_TOM(this); + if (data&0x02) keyOn_CYM(this); else keyOff_CYM(this); + if (data&0x01) keyOn_HH(this); else keyOff_HH(this); + } + slotUpdateAll(&this->ch[6].mod); + slotUpdateAll(&this->ch[6].car); + slotUpdateAll(&this->ch[7].mod); + slotUpdateAll(&this->ch[7].car); + slotUpdateAll(&this->ch[8].mod); + slotUpdateAll(&this->ch[8].car); + + this->reg[rg] = data; + break; + } + if ((rg&0xf) > 8) { + // 0xa9-0xaf 0xb9-0xbf + break; + } + if (!(rg&0x10)) { + // 0xa0-0xa8 + int c = rg-0xa0; + int fNum = data + ((this->reg[rg+0x10]&3)<<8); + int block = (this->reg[rg+0x10]>>2)&7; + channelSetFnumber(&this->ch[c], fNum); + switch (c) { + case 7: this->noiseA_dphase = dphaseNoiseTable[fNum][block]; + break; + case 8: this->noiseB_dphase = dphaseNoiseTable[fNum][block]; + break; + } + slotUpdateAll(&this->ch[c].car); + slotUpdateAll(&this->ch[c].mod); + this->reg[rg] = data; + } else { + // 0xb0-0xb8 + int c = rg-0xb0; + int fNum = ((data&3)<<8) + this->reg[rg-0x10]; + int block = (data>>2)&7; + channelSetFnumber(&this->ch[c], fNum); + channelSetBlock(&this->ch[c], block); + switch (c) { + case 7: this->noiseA_dphase = dphaseNoiseTable[fNum][block]; + break; + case 8: this->noiseB_dphase = dphaseNoiseTable[fNum][block]; + break; + } + if (data&0x20) + keyOn(&this->ch[c]); + else + keyOff(&this->ch[c]); + slotUpdateAll(&this->ch[c].mod); + slotUpdateAll(&this->ch[c].car); + this->reg[rg] = data; + } + break; + } + case 0xc0: { + if (rg > 0xc8) + break; + int c = rg-0xC0; + this->slot[c*2]->patch.FB = (data>>1)&7; + this->ch[c].alg = data&1; + this->reg[rg] = data; + } + } + + //TODO only for registers that influence sound + checkMute(this); +} + +byte OPL_readReg(struct Y8950* this, byte rg) +{ + byte result; + switch (rg) { + case 0x05: // (KEYBOARD IN) + result = 0xff; + break; + + case 0x0f: // ADPCM-DATA + case 0x13: // ??? + case 0x14: // ??? + case 0x1a: // PCM-DATA + result = ADPCM_readReg(&this->adpcm, rg); + break; + + case 0x19: // I/O DATA TODO + /* result = ~(switchGetAudio() ? 0 : 0x04); */ + result = 0; + break; + default: + result = 255; + } + //PRT_DEBUG("Y8950 read " << (int)rg<<" "<<(int)result); + return result; +} + +byte OPL_readStatus(struct Y8950* this) +{ + OPL_setStatus(this, STATUS_BUF_RDY); // temp hack + byte tmp = this->status & (0x80 | this->statusMask); + //PRT_DEBUG("Y8950 read status " << (int)tmp); + return tmp | 0x06; // bit 1 and 2 are always 1 +} + + +void OPL_setStatus(struct Y8950* this, byte flags) +{ + this->status |= flags; + if (this->status & this->statusMask) { + this->status |= 0x80; + /* irq.set(); */ + } +} +void OPL_resetStatus(struct Y8950* this, byte flags) +{ + this->status &= ~flags; + if (!(this->status & this->statusMask)) { + this->status &= 0x7f; + /* irq.reset(); */ + } +} +void OPL_changeStatusMask(struct Y8950* this, byte newMask) +{ + this->statusMask = newMask; + this->status &= this->statusMask; + if (this->status) { + this->status |= 0x80; + /* irq.set(); */ + } else { + this->status &= 0x7f; + /* irq.reset(); */ + } +} diff --git a/apps/codecs/libgme/emu8950.h b/apps/codecs/libgme/emu8950.h new file mode 100644 index 0000000000..88d17b956e --- /dev/null +++ b/apps/codecs/libgme/emu8950.h @@ -0,0 +1,248 @@ +#ifndef __Y8950_HH__ +#define __Y8950_HH__ + +#include "blargg_common.h" +#include "emuadpcm.h" + +#define AUDIO_MONO_BUFFER_SIZE 1024 + +// Dynamic range of envelope +static const double EG_STEP = 0.1875; +#define EG_BITS 9 +#define EG_MUTE (1< + int *plfo_pm; + int *plfo_am; +}; + +void slotReset(struct Slot* slot); + + +struct OPLChannel { + bool alg; + struct Slot mod, car; +}; + +void channelReset(struct OPLChannel* ch); + + +struct Y8950 +{ + int adr; + int output[2]; + // Register + byte reg[0x100]; + bool rythm_mode; + // Pitch Modulator + int pm_mode; + unsigned int pm_phase; + // Amp Modulator + int am_mode; + unsigned int am_phase; + + // Noise Generator + int noise_seed; + int whitenoise; + int noiseA; + int noiseB; + unsigned int noiseA_phase; + unsigned int noiseB_phase; + unsigned int noiseA_dphase; + unsigned int noiseB_dphase; + + // Channel & Slot + struct OPLChannel ch[9]; + struct Slot *slot[18]; + + unsigned int pm_dphase; + int lfo_pm; + unsigned int am_dphase; + int lfo_am; + + int maxVolume; + bool internalMuted; + + int clockRate; + + /** STATUS Register. */ + byte status; + /** bit=0 -> masked. */ + byte statusMask; + /* MsxAudioIRQHelper irq; */ + + // ADPCM + struct Y8950Adpcm adpcm; + + /** 13-bit (exponential) DAC. */ + /* DACSound16S dac13; */ + + // DAC stuff + int dacSampleVolume; + int dacOldSampleVolume; + int dacSampleVolumeSum; + int dacCtrlVolume; + int dacDaVolume; + int dacEnabled; + + // Internal buffer + int buffer[AUDIO_MONO_BUFFER_SIZE]; +}; + +void OPL_init(struct Y8950* this_, byte* ramBank, int sampleRam); + +void OPL_reset(struct Y8950* this_); +void OPL_writeReg(struct Y8950* this_, byte reg, byte data); +byte OPL_readReg(struct Y8950* this_, byte reg); +byte OPL_readStatus(struct Y8950* this_); +static inline void OPL_setInternalMute(struct Y8950* this_, bool muted) { this_->internalMuted = muted; } +static inline bool OPL_isInternalMuted(struct Y8950* this_) { return this_->internalMuted; } + +void OPL_setSampleRate(struct Y8950* this_, int sampleRate, int clockRate); +int* OPL_updateBuffer(struct Y8950* this_, int length); + +// SoundDevice +void OPL_setInternalVolume(struct Y8950* this_, short maxVolume); + +void OPL_setStatus(struct Y8950* this_, byte flags); +void OPL_resetStatus(struct Y8950* this_, byte flags); +void OPL_changeStatusMask(struct Y8950* this_, byte newMask); + + +// Adjust envelope speed which depends on sampling rate +static inline unsigned int rate_adjust(double x, int rate, int clk) +{ + double tmp = x * clk / 72 / rate + 0.5; // +0.5 to round +// assert (tmp <= 4294967295U); + return (unsigned int)tmp; +} + +#endif diff --git a/apps/codecs/libgme/emuadpcm.c b/apps/codecs/libgme/emuadpcm.c new file mode 100644 index 0000000000..1f2d5aefa5 --- /dev/null +++ b/apps/codecs/libgme/emuadpcm.c @@ -0,0 +1,298 @@ +/* + * This file is based on: + * Y8950Adpcm.cc -- Y8950 ADPCM emulator from the openMSX team + * ported to c by gama + * + * The openMSX version is based on: + * emuadpcm.c -- Y8950 ADPCM emulator written by Mitsutaka Okazaki 2001 + * heavily rewritten to fit openMSX structure + */ + +#include + +#include "emuadpcm.h" +#include "emu8950.h" + +// Relative volume between ADPCM part and FM part, +// value experimentally found by Manuel Bilderbeek +const int ADPCM_VOLUME = 356; + +// Bitmask for register 0x07 +static const int R07_RESET = 0x01; +//static const int R07 = 0x02;. // not used +//static const int R07 = 0x04;. // not used +const int R07_SP_OFF = 0x08; +const int R07_REPEAT = 0x10; +const int R07_MEMORY_DATA = 0x20; +const int R07_REC = 0x40; +const int R07_START = 0x80; + +//Bitmask for register 0x08 +const int R08_ROM = 0x01; +const int R08_64K = 0x02; +const int R08_DA_AD = 0x04; +const int R08_SAMPL = 0x08; +//const int R08 = 0x10;. // not used +//const int R08 = 0x20;. // not used +const int R08_NOTE_SET = 0x40; +const int R08_CSM = 0x80; + +const int DMAX = 0x6000; +const int DMIN = 0x7F; +const int DDEF = 0x7F; + +const int DECODE_MAX = 32767; +const int DECODE_MIN = -32768; + +#define GETA_BITS 14 +#define MAX_STEP (1<<(16+GETA_BITS)) + + +//**************************************************// +// // +// Helper functions // +// // +//**************************************************// + +int CLAP(int min, int x, int max) +{ + return (x < min) ? min : ((max < x) ? max : x); +} + +//**********************************************************// +// // +// Y8950Adpcm // +// // +//**********************************************************// + + +void ADPCM_init(struct Y8950Adpcm* this_, struct Y8950* y8950_, byte* ramBank, int sampleRam) + +{ + this_->y8950 = y8950_; + this_->ramBank = ramBank; + this_->ramSize = sampleRam; + memset(this_->ramBank, 0xFF, this_->ramSize); + this_->volume = 0; +} + +void restart(struct Y8950Adpcm* this_); +void ADPCM_reset(struct Y8950Adpcm* this_) +{ + this_->playing = false; + this_->startAddr = 0; + this_->stopAddr = 7; + this_->memPntr = 0; + this_->delta = 0; + this_->step = 0; + this_->addrMask = (1 << 19) - 1; + this_->reg7 = 0; + this_->reg15 = 0; + ADPCM_writeReg(this_, 0x12, 255); // volume + restart(this_); +} + +void ADPCM_setSampleRate(struct Y8950Adpcm* this_, int sr, int clk) +{ + this_->sampleRate = sr; + this_->clockRate = clk; +} + +bool ADPCM_muted(struct Y8950Adpcm* this_) +{ + return (!this_->playing) || (this_->reg7 & R07_SP_OFF); +} + +//**************************************************// +// // +// I/O Ctrl // +// // +//**************************************************// + +void restart(struct Y8950Adpcm* this_) +{ + this_->playAddr = this_->startAddr & this_->addrMask; + this_->nowStep = MAX_STEP - this_->step; + this_->out = this_->output = 0; + this_->diff = DDEF; + this_->nextLeveling = 0; + this_->sampleStep = 0; + this_->volumeWStep = (int)((double)this_->volume * this_->step / MAX_STEP); +} + +void ADPCM_writeReg(struct Y8950Adpcm* this_, byte rg, byte data) +{ + switch (rg) { + case 0x07: // START/REC/MEM DATA/REPEAT/SP-OFF/-/-/RESET + this_->reg7 = data; + if (this_->reg7 & R07_RESET) { + this_->playing = false; + } else if (data & R07_START) { + this_->playing = true; + restart(this_); + } + break; + + case 0x08: // CSM/KEY BOARD SPLIT/-/-/SAMPLE/DA AD/64K/ROM + this_->romBank = data & R08_ROM; + this_->addrMask = data & R08_64K ? (1<<17)-1 : (1<<19)-1; + break; + + case 0x09: // START ADDRESS (L) + this_->startAddr = (this_->startAddr & 0x7F800) | (data << 3); + this_->memPntr = 0; + break; + case 0x0A: // START ADDRESS (H) + this_->startAddr = (this_->startAddr & 0x007F8) | (data << 11); + this_->memPntr = 0; + break; + + case 0x0B: // STOP ADDRESS (L) + this_->stopAddr = (this_->stopAddr & 0x7F807) | (data << 3); + break; + case 0x0C: // STOP ADDRESS (H) + this_->stopAddr = (this_->stopAddr & 0x007FF) | (data << 11); + break; + + + case 0x0F: // ADPCM-DATA + // TODO check this + //if ((reg7 & R07_REC) && (reg7 & R07_MEMORY_DATA)) { + { + int tmp = ((this_->startAddr + this_->memPntr) & this_->addrMask) / 2; + tmp = (tmp < this_->ramSize) ? tmp : (tmp & (this_->ramSize - 1)); + if (!this_->romBank) { + this_->ramBank[tmp] = data; + } + //PRT_DEBUG("Y8950Adpcm: mem " << tmp << " " << (int)data); + this_->memPntr += 2; + if ((this_->startAddr + this_->memPntr) > this_->stopAddr) { + OPL_setStatus(this_->y8950, STATUS_EOS); + } + } + OPL_setStatus(this_->y8950, STATUS_BUF_RDY); + break; + + case 0x10: // DELTA-N (L) + this_->delta = (this_->delta & 0xFF00) | data; + this_->step = rate_adjust(this_->delta<sampleRate, this_->clockRate); + this_->volumeWStep = (int)((double)this_->volume * this_->step / MAX_STEP); + break; + case 0x11: // DELTA-N (H) + this_->delta = (this_->delta & 0x00FF) | (data << 8); + this_->step = rate_adjust(this_->delta<sampleRate, this_->clockRate); + this_->volumeWStep = (int)((double)this_->volume * this_->step / MAX_STEP); + break; + + case 0x12: { // ENVELOP CONTROL + int oldVol = this_->volume; + this_->volume = (data * ADPCM_VOLUME) >> 8; + if (oldVol != 0) { + double factor = (double)this_->volume / (double)oldVol; + this_->output = (int)((double)this_->output * factor); + this_->sampleStep = (int)((double)this_->sampleStep * factor); + } + this_->volumeWStep = (int)((double)this_->volume * this_->step / MAX_STEP); + break; + } + case 0x0D: // PRESCALE (L) + case 0x0E: // PRESCALE (H) + case 0x15: // DAC-DATA (bit9-2) + case 0x16: // (bit1-0) + case 0x17: // (exponent) + case 0x1A: // PCM-DATA + // not implemented + break; + } +} + +byte ADPCM_readReg(struct Y8950Adpcm* this_, byte rg) +{ + byte result; + switch (rg) { + case 0x0F: { // ADPCM-DATA + // TODO don't advance pointer when playing??? + int adr = ((this_->startAddr + this_->memPntr) & this_->addrMask) / 2; + if (this_->romBank || (adr >= this_->ramSize)) { + result = 0xFF; + } else { + result = this_->ramBank[adr]; + } + this_->memPntr += 2; + if ((this_->startAddr + this_->memPntr) > this_->stopAddr) { + OPL_setStatus(this_->y8950, STATUS_EOS); + } + break; + } + case 0x13: // TODO check + result = this_->out & 0xFF; + break; + case 0x14: // TODO check + result = this_->out / 256; + break; + default: + result = 255; + } + //PRT_DEBUG("Y8950Adpcm: read "<<(int)rg<<" "<<(int)result); + return result; +} + +int ADPCM_calcSample(struct Y8950Adpcm* this_) +{ + // This table values are from ymdelta.c by Tatsuyuki Satoh. + static const int F1[16] = { 1, 3, 5, 7, 9, 11, 13, 15, + -1, -3, -5, -7, -9, -11, -13, -15}; + static const int F2[16] = {57, 57, 57, 57, 77, 102, 128, 153, + 57, 57, 57, 57, 77, 102, 128, 153}; + + if (ADPCM_muted(this_)) { + return 0; + } + this_->nowStep += this_->step; + if (this_->nowStep >= MAX_STEP) { + int nowLeveling; + do { + this_->nowStep -= MAX_STEP; + unsigned long val; + if (!(this_->playAddr & 1)) { + // n-th nibble + int tmp = this_->playAddr / 2; + if (this_->romBank || (tmp >= this_->ramSize)) { + this_->reg15 = 0xFF; + } else { + this_->reg15 = this_->ramBank[tmp]; + } + val = this_->reg15 >> 4; + } else { + // (n+1)-th nibble + val = this_->reg15 & 0x0F; + } + int prevOut = this_->out; + this_->out = CLAP(DECODE_MIN, this_->out + (this_->diff * F1[val]) / 8, + DECODE_MAX); + this_->diff = CLAP(DMIN, (this_->diff * F2[val]) / 64, DMAX); + int deltaNext = this_->out - prevOut; + nowLeveling = this_->nextLeveling; + this_->nextLeveling = prevOut + deltaNext / 2; + + this_->playAddr++; + if (this_->playAddr > this_->stopAddr) { + if (this_->reg7 & R07_REPEAT) { + restart(this_); + } else { + this_->playing = false; + //y8950.setStatus(Y8950::STATUS_EOS); + } + } + } while (this_->nowStep >= MAX_STEP); + this_->sampleStep = (this_->nextLeveling - nowLeveling) * this_->volumeWStep; + this_->output = nowLeveling * this_->volume; + + /* TODO: Used fixed point math here */ + #if !defined(ROCKBOX) + this_->output += (int)((double)this_->sampleStep * ((double)this_->nowStep/(double)this_->step)); + #endif + } + this_->output += this_->sampleStep; + return this_->output >> 12; +} diff --git a/apps/codecs/libgme/emuadpcm.h b/apps/codecs/libgme/emuadpcm.h new file mode 100644 index 0000000000..0fc39a1709 --- /dev/null +++ b/apps/codecs/libgme/emuadpcm.h @@ -0,0 +1,52 @@ +#ifndef __Y8950ADPCM_HH__ +#define __Y8950ADPCM_HH__ + +#include "blargg_common.h" +#include "blargg_source.h" +#include "msxtypes.h" + +typedef unsigned short word; +typedef unsigned __int64 uint64; +struct Y8950; + +struct Y8950Adpcm +{ + struct Y8950* y8950; + + int sampleRate; + int clockRate; + + int ramSize; + int startAddr; + int stopAddr; + int playAddr; + int addrMask; + int memPntr; + bool romBank; + byte* ramBank; + + bool playing; + int volume; + word delta; + unsigned int nowStep, step; + int out, output; + int diff; + int nextLeveling; + int sampleStep; + int volumeWStep; + + byte reg7; + byte reg15; +}; + + +void ADPCM_init(struct Y8950Adpcm* this_, struct Y8950* y8950, byte* ramBank, int sampleRam); +void ADPCM_reset(struct Y8950Adpcm* this_); +void ADPCM_setSampleRate(struct Y8950Adpcm* this_, int sr, int clk); +bool ADPCM_muted(struct Y8950Adpcm* this_); +void ADPCM_writeReg(struct Y8950Adpcm* this_, byte rg, byte data); +byte ADPCM_readReg(struct Y8950Adpcm* this_, byte rg); +int ADPCM_calcSample(struct Y8950Adpcm* this_); + + +#endif diff --git a/apps/codecs/libgme/emutables.h b/apps/codecs/libgme/emutables.h new file mode 100644 index 0000000000..53fb324cdd --- /dev/null +++ b/apps/codecs/libgme/emutables.h @@ -0,0 +1,170 @@ +#ifndef _EMUTABLES_H_ +#define _EMUTABLES_H_ + +/* Precalculated emu2413 tables for use in Rockbox, + Calculated for 44Khz sampling rate */ + +#include "emutypes.h" + +static const e_uint16 sin_coeff[] ICONST_ATTR = { + 255, 203, 171, 152, 139, 129, 120, + 113, 107, 102, 97, 92, 88, 85, + 81, 78, 75, 72, 70, 67, 65, + 63, 61, 59, 57, 55, 53, 52, + 50, 48, 47, 45, 44, 43, 41, + 40, 39, 38, 37, 35, 34, 33, + 32, 31, 30, 29, 28, 28, 27, + 26, 25, 24, 23, 23, 22, 21, + 21, 20, 19, 19, 18, 17, 17, + 16, 16, 15, 14, 14, 13, 13, + 12, 12, 11, 11, 11, 10, 10, + 9, 9, 8, 8, 8, 7, 7, + 7, 6, 6, 6, 5, 5, 5, + 4, 4, 4, 4, 3, 3, 3, + 3, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, +}; + +static const e_int32 pm_coeff[] ICONST_ATTR = { + 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, + 258, 258, 258, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 256, + 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 254, + 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 253, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, + 254, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, +}; + +static const e_int16 db2lin_coeff[] ICONST_ATTR = { + 255, 249, 244, 239, 233, 228, 224, + 219, 214, 209, 205, 201, 196, 192, + 188, 184, 180, 176, 172, 169, 165, + 162, 158, 155, 151, 148, 145, 142, + 139, 136, 133, 130, 127, 125, 122, + 119, 117, 114, 112, 109, 107, 105, + 102, 100, 98, 96, 94, 92, 90, + 88, 86, 84, 82, 81, 79, 77, + 76, 74, 72, 71, 69, 68, 66, + 65, 64, 62, 61, 60, 58, 57, + 56, 55, 53, 52, 51, 50, 49, + 48, 47, 46, 45, 44, 43, 42, + 41, 40, 39, 38, 38, 37, 36, + 35, 34, 34, 33, 32, 32, 31, + 30, 30, 29, 28, 28, 27, 27, + 26, 25, 25, 24, 24, 23, 23, + 22, 22, 21, 21, 20, 20, 19, + 19, 19, 18, 18, 17, 17, 17, + 16, 16, 16, 15, 15, 15, 14, + 14, 14, 13, 13, 13, 12, 12, + 12, 12, 11, 11, 11, 11, 10, + 10, 10, 10, 10, 9, 9, 9, + 9, 8, 8, 8, 8, 8, 8, + 7, 7, 7, 7, 7, 7, 6, + 6, 6, 6, 6, 6, 6, 5, + 5, 5, 5, 5, 5, 5, 5, + 5, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, + 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static const e_uint16 ar_adjust_coeff[] ICONST_ATTR = { + 127, 108, 98, 90, 84, 80, 75, + 72, 69, 66, 64, 61, 59, 57, + 56, 54, 52, 51, 49, 48, 47, + 45, 44, 43, 42, 41, 40, 39, + 38, 37, 36, 36, 35, 34, 33, + 33, 32, 31, 30, 30, 29, 29, + 28, 27, 27, 26, 26, 25, 24, + 24, 23, 23, 22, 22, 21, 21, + 21, 20, 20, 19, 19, 18, 18, + 17, 17, 17, 16, 16, 15, 15, + 15, 14, 14, 14, 13, 13, 13, + 12, 12, 12, 11, 11, 11, 10, + 10, 10, 9, 9, 9, 9, 8, + 8, 8, 7, 7, 7, 7, 6, + 6, 6, 6, 5, 5, 5, 4, + 4, 4, 4, 4, 3, 3, 3, + 3, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 0, 0, 0, 0, + 0, +}; + +#endif diff --git a/apps/codecs/libgme/emutypes.h b/apps/codecs/libgme/emutypes.h new file mode 100644 index 0000000000..bf5d7e1bf2 --- /dev/null +++ b/apps/codecs/libgme/emutypes.h @@ -0,0 +1,41 @@ +#ifndef _EMUTYPES_H_ +#define _EMUTYPES_H_ + +#if defined(_MSC_VER) +#define INLINE __forceinline +#elif defined(__GNUC__) +#define INLINE __inline__ +#elif defined(_MWERKS_) +#define INLINE inline +#else +#define INLINE +#endif + +#if defined(EMU_DLL_IMPORTS) +#define EMU2149_DLL_IMPORTS +#define EMU2212_DLL_IMPORTS +#define EMU2413_DLL_IMPORTS +#define EMU8950_DLL_IMPORTS +#define EMU76489_DLL_IMPORTS +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef unsigned int e_uint; +typedef signed int e_int; + +typedef unsigned char e_uint8 ; +typedef signed char e_int8 ; + +typedef unsigned short e_uint16 ; +typedef signed short e_int16 ; + +typedef unsigned int e_uint32 ; +typedef signed int e_int32 ; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/apps/codecs/libgme/gb_apu.c b/apps/codecs/libgme/gb_apu.c new file mode 100644 index 0000000000..a441645e3e --- /dev/null +++ b/apps/codecs/libgme/gb_apu.c @@ -0,0 +1,410 @@ +// Gb_Snd_Emu 0.1.4. http://www.slack.net/~ant/ + +#include "gb_apu.h" + +//#include "gb_apu_logger.h" + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const vol_reg = 0xFF24; +int const stereo_reg = 0xFF25; +int const status_reg = 0xFF26; +int const wave_ram = 0xFF30; + +int const power_mask = 0x80; + +inline int calc_output( struct Gb_Apu* this, int osc ) +{ + int bits = this->regs [stereo_reg - io_addr] >> osc; + return (bits >> 3 & 2) | (bits & 1); +} + +void Apu_set_output( struct Gb_Apu* this, int i, struct Blip_Buffer* center, struct Blip_Buffer* left, struct Blip_Buffer* right ) +{ + // Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL) + require( !center || (center && !left && !right) || (center && left && right) ); + require( (unsigned) i < osc_count ); // fails if you pass invalid osc index + + if ( !center || !left || !right ) + { + left = center; + right = center; + } + + struct Gb_Osc* o = this->oscs [i]; + o->outputs [1] = right; + o->outputs [2] = left; + o->outputs [3] = center; + o->output = o->outputs [calc_output( this, i )]; +} + +void synth_volume( struct Gb_Apu* this, int iv ) +{ + double v = this->volume_ * 0.60 / osc_count / 15 /*steps*/ / 8 /*master vol range*/ * iv; + Synth_volume( &this->synth, v ); +} + +void apply_volume( struct Gb_Apu* this ) +{ + // TODO: Doesn't handle differing left and right volumes (panning). + // Not worth the complexity. + int data = this->regs [vol_reg - io_addr]; + int left = data >> 4 & 7; + int right = data & 7; + //if ( data & 0x88 ) dprintf( "Vin: %02X\n", data & 0x88 ); + //if ( left != right ) dprintf( "l: %d r: %d\n", left, right ); + synth_volume( this, max( left, right ) + 1 ); +} + +void Apu_volume( struct Gb_Apu* this, double v ) +{ + if ( this->volume_ != v ) + { + this->volume_ = v; + apply_volume( this ); + } +} + +void reset_regs( struct Gb_Apu* this ) +{ + int i; + for ( i = 0; i < 0x20; i++ ) + this->regs [i] = 0; + + Sweep_reset ( &this->square1 ); + Square_reset( &this->square2 ); + Wave_reset ( &this->wave ); + Noise_reset ( &this->noise ); + + apply_volume( this ); +} + +void reset_lengths( struct Gb_Apu* this ) +{ + this->square1.osc.length_ctr = 64; + this->square2.osc.length_ctr = 64; + this->wave .osc.length_ctr = 256; + this->noise .osc.length_ctr = 64; +} + +void Apu_reduce_clicks( struct Gb_Apu* this, bool reduce ) +{ + this->reduce_clicks_ = reduce; + + // Click reduction makes DAC off generate same output as volume 0 + int dac_off_amp = 0; + if ( reduce && this->wave.osc.mode != mode_agb ) // AGB already eliminates clicks + dac_off_amp = -dac_bias; + + int i; + for ( i = 0; i < osc_count; i++ ) + this->oscs [i]->dac_off_amp = dac_off_amp; + + // AGB always eliminates clicks on wave channel using same method + if ( this->wave.osc.mode == mode_agb ) + this->wave.osc.dac_off_amp = -dac_bias; +} + +void Apu_reset( struct Gb_Apu* this, enum gb_mode_t mode, bool agb_wave ) +{ + // Hardware mode + if ( agb_wave ) + mode = mode_agb; // using AGB wave features implies AGB hardware + this->wave.agb_mask = agb_wave ? 0xFF : 0; + int i; + for ( i = 0; i < osc_count; i++ ) + this->oscs [i]->mode = mode; + Apu_reduce_clicks( this, this->reduce_clicks_ ); + + // Reset state + this->frame_time = 0; + this->last_time = 0; + this->frame_phase = 0; + + reset_regs( this ); + reset_lengths( this ); + + // Load initial wave RAM + static byte const initial_wave [2] [16] ICONST_ATTR = { + {0x84,0x40,0x43,0xAA,0x2D,0x78,0x92,0x3C,0x60,0x59,0x59,0xB0,0x34,0xB8,0x2E,0xDA}, + {0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF}, + }; + int b; + for ( b = 2; --b >= 0; ) + { + // Init both banks (does nothing if not in AGB mode) + // TODO: verify that this works + Apu_write_register( this, 0, 0xFF1A, b * 0x40 ); + unsigned i; + for ( i = 0; i < sizeof initial_wave [0]; i++ ) + Apu_write_register( this, 0, i + wave_ram, initial_wave [(mode != mode_dmg)] [i] ); + } +} + +void Apu_set_tempo( struct Gb_Apu* this, double t ) +{ + this->frame_period = 4194304 / 512; // 512 Hz + if ( t != 1.0 ) + this->frame_period = t ? (blip_time_t) (this->frame_period / t) : (blip_time_t) (0); +} + +void Apu_init( struct Gb_Apu* this ) +{ + this->wave.wave_ram = &this->regs [wave_ram - io_addr]; + + Synth_init( &this->synth ); + + this->oscs [0] = &this->square1.osc; + this->oscs [1] = &this->square2.osc; + this->oscs [2] = &this->wave.osc; + this->oscs [3] = &this->noise.osc; + + int i; + for ( i = osc_count; --i >= 0; ) + { + struct Gb_Osc* o = this->oscs [i]; + o->regs = &this->regs [i * 5]; + o->output = NULL; + o->outputs [0] = NULL; + o->outputs [1] = NULL; + o->outputs [2] = NULL; + o->outputs [3] = NULL; + o->synth = &this->synth; + } + + this->reduce_clicks_ = false; + Apu_set_tempo( this, 1.0 ); + this->volume_ = 1.0; + Apu_reset( this, mode_cgb, false ); +} + +void run_until_( struct Gb_Apu* this, blip_time_t end_time ) +{ + if ( !this->frame_period ) + this->frame_time += end_time - this->last_time; + + while ( true ) + { + // run oscillators + blip_time_t time = end_time; + if ( time > this->frame_time ) + time = this->frame_time; + + Square_run( &this->square1, this->last_time, time ); + Square_run( &this->square2, this->last_time, time ); + Wave_run ( &this->wave, this->last_time, time ); + Noise_run ( &this->noise, this->last_time, time ); + this->last_time = time; + + if ( time == end_time ) + break; + + // run frame sequencer + assert( this->frame_period ); + this->frame_time += this->frame_period * clk_mul; + switch ( this->frame_phase++ ) + { + case 2: + case 6: + // 128 Hz + clock_sweep( &this->square1 ); + case 0: + case 4: + // 256 Hz + Osc_clock_length( &this->square1.osc ); + Osc_clock_length( &this->square2.osc); + Osc_clock_length( &this->wave.osc); + Osc_clock_length( &this->noise.osc); + break; + + case 7: + // 64 Hz + this->frame_phase = 0; + Square_clock_envelope( &this->square1 ); + Square_clock_envelope( &this->square2 ); + Noise_clock_envelope( &this->noise ); + } + } +} + +inline void run_until( struct Gb_Apu* this, blip_time_t time ) +{ + require( time >= this->last_time ); // end_time must not be before previous time + if ( time > this->last_time ) + run_until_( this, time ); +} + +void Apu_end_frame( struct Gb_Apu* this, blip_time_t end_time ) +{ + #ifdef LOG_FRAME + LOG_FRAME( end_time ); + #endif + + if ( end_time > this->last_time ) + run_until( this, end_time ); + + this->frame_time -= end_time; + assert( this->frame_time >= 0 ); + + this->last_time -= end_time; + assert( this->last_time >= 0 ); +} + +void silence_osc( struct Gb_Apu* this, struct Gb_Osc* o ) +{ + int delta = -o->last_amp; + if ( this->reduce_clicks_ ) + delta += o->dac_off_amp; + + if ( delta ) + { + o->last_amp = o->dac_off_amp; + if ( o->output ) + { + Blip_set_modified( o->output ); + Synth_offset( &this->synth, this->last_time, delta, o->output ); + } + } +} + +void apply_stereo( struct Gb_Apu* this ) +{ + int i; + for ( i = osc_count; --i >= 0; ) + { + struct Gb_Osc* o = this->oscs [i]; + struct Blip_Buffer* out = o->outputs [calc_output( this, i )]; + if ( o->output != out ) + { + silence_osc( this, o ); + o->output = out; + } + } +} + +void Apu_write_register( struct Gb_Apu* this, blip_time_t time, int addr, int data ) +{ + require( (unsigned) data < 0x100 ); + + int reg = addr - io_addr; + if ( (unsigned) reg >= io_size ) + { + require( false ); + return; + } + + #ifdef LOG_WRITE + LOG_WRITE( time, addr, data ); + #endif + + if ( addr < status_reg && !(this->regs [status_reg - io_addr] & power_mask) ) + { + // Power is off + + // length counters can only be written in DMG mode + if ( this->wave.osc.mode != mode_dmg || (reg != 1 && reg != 5+1 && reg != 10+1 && reg != 15+1) ) + return; + + if ( reg < 10 ) + data &= 0x3F; // clear square duty + } + + run_until( this, time ); + + if ( addr >= wave_ram ) + { + Wave_write( &this->wave, addr, data ); + } + else + { + int old_data = this->regs [reg]; + this->regs [reg] = data; + + if ( addr < vol_reg ) + { + // Oscillator + write_osc( this, reg, old_data, data ); + } + else if ( addr == vol_reg && data != old_data ) + { + // Master volume + int i; + for ( i = osc_count; --i >= 0; ) + silence_osc( this, this->oscs [i] ); + + apply_volume( this ); + } + else if ( addr == stereo_reg ) + { + // Stereo panning + apply_stereo( this ); + } + else if ( addr == status_reg && (data ^ old_data) & power_mask ) + { + // Power control + this->frame_phase = 0; + int i; + for ( i = osc_count; --i >= 0; ) + silence_osc( this, this->oscs [i] ); + + reset_regs( this ); + if ( this->wave.osc.mode != mode_dmg ) + reset_lengths( this ); + + this->regs [status_reg - io_addr] = data; + } + } +} + +int Apu_read_register( struct Gb_Apu* this, blip_time_t time, int addr ) +{ + if ( addr >= status_reg ) + run_until( this, time ); + + int reg = addr - io_addr; + if ( (unsigned) reg >= io_size ) + { + require( false ); + return 0; + } + + if ( addr >= wave_ram ) + return Wave_read( &this->wave, addr ); + + // Value read back has some bits always set + static byte const masks [] ICONST_ATTR = { + 0x80,0x3F,0x00,0xFF,0xBF, + 0xFF,0x3F,0x00,0xFF,0xBF, + 0x7F,0xFF,0x9F,0xFF,0xBF, + 0xFF,0xFF,0x00,0x00,0xBF, + 0x00,0x00,0x70, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF + }; + int mask = masks [reg]; + if ( this->wave.agb_mask && (reg == 10 || reg == 12) ) + mask = 0x1F; // extra implemented bits in wave regs on AGB + int data = this->regs [reg] | mask; + + // Status register + if ( addr == status_reg ) + { + data &= 0xF0; + data |= (int) this->square1.osc.enabled << 0; + data |= (int) this->square2.osc.enabled << 1; + data |= (int) this->wave .osc.enabled << 2; + data |= (int) this->noise .osc.enabled << 3; + } + + return data; +} diff --git a/apps/codecs/libgme/gb_apu.h b/apps/codecs/libgme/gb_apu.h new file mode 100644 index 0000000000..f1457a2bbe --- /dev/null +++ b/apps/codecs/libgme/gb_apu.h @@ -0,0 +1,85 @@ +// Nintendo Game Boy sound hardware emulator with save state support + +// Gb_Snd_Emu 0.1.4 +#ifndef GB_APU_H +#define GB_APU_H + +#include "gb_oscs.h" + +// Clock rate sound hardware runs at +enum { clock_rate = 4194304 * GB_APU_OVERCLOCK }; + +// Registers are at io_addr to io_addr+io_size-1 +enum { io_addr = 0xFF10 }; +enum { io_size = 0x30 }; +enum { regs_size = io_size + 0x10 }; + +enum gb_mode_t { + mode_dmg, // Game Boy monochrome + mode_cgb, // Game Boy Color + mode_agb // Game Boy Advance +}; + +// 0: Square 1, 1: Square 2, 2: Wave, 3: Noise +enum { osc_count = 4 }; // 0 <= chan < osc_count + +struct Gb_Apu { + struct Gb_Osc* oscs [osc_count]; + blip_time_t last_time; // time sound emulator has been run to + blip_time_t frame_period; // clocks between each frame sequencer step + double volume_; + bool reduce_clicks_; + + struct Gb_Square square1; + struct Gb_Square square2; + struct Gb_Wave wave; + struct Gb_Noise noise; + blip_time_t frame_time; // time of next frame sequencer action + int frame_phase; // phase of next frame sequencer step + + uint8_t regs [regs_size];// last values written to registers + + // large objects after everything else + struct Blip_Synth synth; +}; + +// Basics + +// Initializes apu +void Apu_init( struct Gb_Apu* this ); + +// Emulates to time t, then writes data to addr +void Apu_write_register( struct Gb_Apu* this, blip_time_t t, int addr, int data ) ICODE_ATTR; + +// Emulates to time t, then subtracts t from the current time. +// OK if previous write call had time slightly after t. +void Apu_end_frame( struct Gb_Apu* this,blip_time_t t ) ICODE_ATTR; + +// More features + +// Emulates to time t, then reads from addr +int Apu_read_register( struct Gb_Apu* this, blip_time_t t, int addr ) ICODE_ATTR; + +// Resets hardware to state after power, BEFORE boot ROM runs. Mode selects +// sound hardware. If agb_wave is true, enables AGB's extra wave features. +void Apu_reset( struct Gb_Apu* this, enum gb_mode_t mode, bool agb_wave ); + +// Same as set_output(), but for a particular channel +void Apu_set_output( struct Gb_Apu* this, int chan, struct Blip_Buffer* center, + struct Blip_Buffer* left, struct Blip_Buffer* right ); + +// Sets overall volume, where 1.0 is normal +void Apu_volume( struct Gb_Apu* this, double v ); + +// If true, reduces clicking by disabling DAC biasing. Note that this reduces +// emulation accuracy, since the clicks are authentic. +void Apu_reduce_clicks( struct Gb_Apu* this, bool reduce ); + +// Sets frame sequencer rate, where 1.0 is normal. Meant for adjusting the +// tempo in a music player. +void Apu_set_tempo( struct Gb_Apu* this, double t ); + + +void write_osc( struct Gb_Apu* this, int reg, int old_data, int data ) ICODE_ATTR; + +#endif diff --git a/apps/codecs/libgme/gb_cpu.c b/apps/codecs/libgme/gb_cpu.c new file mode 100644 index 0000000000..ae19cc06c1 --- /dev/null +++ b/apps/codecs/libgme/gb_cpu.c @@ -0,0 +1,53 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "gb_cpu.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +inline void set_code_page( struct Gb_Cpu* this, int i, void* p ) +{ + byte* p2 = STATIC_CAST(byte*,p) - GB_CPU_OFFSET( i * page_size ); + this->cpu_state_.code_map [i] = p2; + this->cpu_state->code_map [i] = p2; +} + +void Cpu_reset( struct Gb_Cpu* this, void* unmapped ) +{ + check( this->cpu_state == &this->cpu_state_ ); + this->cpu_state = &this->cpu_state_; + + this->cpu_state_.time = 0; + + int i; + for ( i = 0; i < page_count + 1; ++i ) + set_code_page( this, i, unmapped ); + + memset( &this->r, 0, sizeof this->r ); + + blargg_verify_byte_order(); +} + +void Cpu_map_code( struct Gb_Cpu* this, addr_t start, int size, void* data ) +{ + // address range must begin and end on page boundaries + require( start % page_size == 0 ); + require( size % page_size == 0 ); + require( start + size <= mem_size ); + + int offset; + for ( offset = 0; offset < size; offset += page_size ) + set_code_page( this, (start + offset) >> page_bits, STATIC_CAST(char*,data) + offset ); +} diff --git a/apps/codecs/libgme/gb_cpu.h b/apps/codecs/libgme/gb_cpu.h new file mode 100644 index 0000000000..3a3b1d6101 --- /dev/null +++ b/apps/codecs/libgme/gb_cpu.h @@ -0,0 +1,80 @@ +// Nintendo Game Boy CPU emulator + +// Game_Music_Emu 0.6-pre +#ifndef GB_CPU_H +#define GB_CPU_H + +#include "blargg_common.h" +#include "blargg_source.h" + +typedef int addr_t; + +// Emulator reads this many bytes past end of a page +enum { cpu_padding = 8 }; +enum { mem_size = 0x10000 }; +enum { page_bits = 13 }; +enum { page_size = 1 << page_bits }; +enum { page_count = mem_size >> page_bits }; + +// Game Boy Z-80 registers. NOT kept updated during emulation. +struct core_regs_t { + uint16_t bc, de, hl, fa; +}; + +struct registers_t { + int pc; // more than 16 bits to allow overflow detection + uint16_t sp; + + struct core_regs_t rp; +}; + +struct cpu_state_t { + byte* code_map [page_count + 1]; + int time; +}; + +struct Gb_Cpu { + // Base address for RST vectors, to simplify GBS player (normally 0) + addr_t rst_base; + + struct registers_t r; + struct cpu_state_t* cpu_state; // points to state_ or a local copy within run() + struct cpu_state_t cpu_state_; +}; + +// Initializes Gb cpu +static inline void Cpu_init( struct Gb_Cpu* this ) +{ + this->rst_base = 0; + this->cpu_state = &this->cpu_state_; +} + +// Clears registers and map all pages to unmapped +void Cpu_reset( struct Gb_Cpu* this, void* unmapped ); + +// Maps code memory (memory accessed via the program counter). Start and size +// must be multiple of page_size. +void Cpu_map_code( struct Gb_Cpu* this, addr_t start, int size, void* code ) ICODE_ATTR; + +// Current time. +static inline int Cpu_time( struct Gb_Cpu* this ) { return this->cpu_state->time; } + +// Changes time. Must not be called during emulation. +// Should be negative, because emulation stops once it becomes >= 0. +static inline void Cpu_set_time( struct Gb_Cpu* this, int t ) { this->cpu_state->time = t; } + +#define GB_CPU_PAGE( addr ) ((unsigned) (addr) >> page_bits) + +#ifdef BLARGG_NONPORTABLE + #define GB_CPU_OFFSET( addr ) (addr) +#else + #define GB_CPU_OFFSET( addr ) ((addr) & (page_size - 1)) +#endif + +// Accesses emulated memory as CPU does +static inline uint8_t* Cpu_get_code( struct Gb_Cpu* this, addr_t addr ) +{ + return this->cpu_state_.code_map [GB_CPU_PAGE( addr )] + GB_CPU_OFFSET( addr ); +} + +#endif diff --git a/apps/codecs/libgme/gb_cpu_run.h b/apps/codecs/libgme/gb_cpu_run.h new file mode 100644 index 0000000000..86f06fa859 --- /dev/null +++ b/apps/codecs/libgme/gb_cpu_run.h @@ -0,0 +1,1187 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#if 0 +/* Define these macros in the source file before #including this file. +- Parameters might be expressions, so they are best evaluated only once, +though they NEVER have side-effects, so multiple evaluation is OK. +- Output parameters might be a multiple-assignment expression like "a=x", +so they must NOT be parenthesized. +- Macros "returning" void may use a {} statement block. */ + + // 0 <= addr <= 0xFFFF + page_size + // time functions can be used + int READ_MEM( addr_t ); + void WRITE_MEM( addr_t, int data ); + + // Access of 0xFF00 + offset + // 0 <= offset <= 0xFF + int READ_IO( int offset ); + void WRITE_IO( int offset, int data ); + + // Often-used instructions use this instead of READ_MEM + void READ_FAST( addr_t, int& out ); + +// The following can be used within macros: + + // Current time + cpu_time_t TIME(); +#endif + +/* Copyright (C) 2003-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +// Common instructions: +// +// 365880 FA LD A,(nn) +// 355863 20 JR NZ +// 313655 21 LD HL,nn +// 274580 28 JR Z +// 252878 FE CP n +// 230541 7E LD A,(HL) +// 226209 2A LD A,(HL+) +// 217467 CD CALL +// 212034 C9 RET +// 208376 CB CB prefix +// +// 27486 CB 7E BIT 7,(HL) +// 15925 CB 76 BIT 6,(HL) +// 13035 CB 19 RR C +// 11557 CB 7F BIT 7,A +// 10898 CB 37 SWAP A +// 10208 CB 66 BIT 4,(HL) + +// Allows MWCW debugger to step through code properly +#ifdef CPU_BEGIN + CPU_BEGIN +#endif + +#define TIME() s.time + +#define CODE_PAGE( addr ) s.code_map [GB_CPU_PAGE( addr )] +#define READ_CODE( addr ) (CODE_PAGE( addr ) [GB_CPU_OFFSET( addr )]) + +// Flags with hex value for clarity when used as mask. +// Stored in indicated variable during emulation. +int const z80 = 0x80; // cz +int const n40 = 0x40; // ph +int const h20 = 0x20; // ph +int const c10 = 0x10; // cz + +#define SET_FLAGS( in )\ +{\ + cz = ((in) << 4 & 0x100) + (~(in) >> 7 & 1);\ + ph = (~(in) << 2 & 0x100) + ((in) >> 1 & 0x10);\ +} + +// random bits in cz to catch misuse of them +#define SET_FLAGS_DEBUG( in )\ +{\ + cz = ((in) << 4 & 0x100) | (rand() & ~0x1FF) | ((in) & 0x80 ? 0 : (rand() & 0xFF) | 1);\ + ph = (~(in) << 2 & 0x100) | (((in) >> 1 & 0x10) ^ BYTE( cz ));\ +} + +#define GET_FLAGS( out )\ +{\ + out = (cz >> 4 & c10);\ + out += ~ph >> 2 & n40;\ + out += (ph ^ cz) << 1 & h20;\ + if ( !BYTE( cz ) )\ + out += z80;\ +} + +#define CC_NZ() ( BYTE( cz )) +#define CC_Z() (!BYTE( cz )) +#define CC_NC() (!(cz & 0x100)) +#define CC_C() ( cz & 0x100 ) + +// Truncation +#define BYTE( n ) ((uint8_t ) (n)) /* (unsigned) n & 0xFF */ +#define SBYTE( n ) ((int8_t ) (n)) /* (BYTE( n ) ^ 0x80) - 0x80 */ +#define WORD( n ) ((uint16_t) (n)) /* (unsigned) n & 0xFFFF */ + +{ + struct cpu_state_t s; + cpu->cpu_state = &s; + memcpy( &s, &cpu->cpu_state_, sizeof s ); + + union { + struct { + #ifdef BLARGG_BIG_ENDIAN + byte b, c, d, e, h, l, flags, a; + #else + byte c, b, e, d, l, h, a, flags; + #endif + } rg; // individual registers + struct core_regs_t rp; // pairs + + byte r8_ [8]; // indexed registers (use R8 macro due to endian dependence) + uint16_t r16 [4]; // indexed pairs + } reg; + BOOST_STATIC_ASSERT( sizeof reg.rg == 8 && sizeof reg.rp == 8 ); + + #ifdef BLARGG_BIG_ENDIAN + #define R8( n ) (reg.r8_ [n]) + #elif BLARGG_LITTLE_ENDIAN + #define R8( n ) (reg.r8_ [(n) ^ 1]) + #else + // Be sure "blargg_endian.h" has been #included in the file that #includes this + #error "Byte order of CPU must be known" + #endif + + #define R16( n ) (reg.r16 [n]) + #define RG (reg.rg) + #define RP (reg.rp) + + RP = cpu->r.rp; + int pc = cpu->r.pc; + int sp = cpu->r.sp; + int ph; + int cz; + SET_FLAGS( RG.flags ); + + int time = s.time; + +loop: + + check( (unsigned) pc < 0x10000 + 1 ); // +1 so emulator can catch wrap-around + check( (unsigned) sp < 0x10000 ); + + byte const* instr = CODE_PAGE( pc ); + int op; + + if ( GB_CPU_OFFSET(~0) == ~0 ) + { + op = instr [pc]; + pc++; + instr += pc; + } + else + { + instr += GB_CPU_OFFSET( pc ); + op = *instr++; + pc++; + } + +#define GET_ADDR() GET_LE16( instr ) + + static byte const instr_times [256*2] ICONST_ATTR = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 4,12, 8, 8, 4, 4, 8, 4,20, 8, 8, 8, 4, 4, 8, 4,// 0 + 4,12, 8, 8, 4, 4, 8, 4,12, 8, 8, 8, 4, 4, 8, 4,// 1 + 8,12, 8, 8, 4, 4, 8, 4, 8, 8, 8, 8, 4, 4, 8, 4,// 2 + 8,12, 8, 8,12,12,12, 4, 8, 8, 8, 8, 4, 4, 8, 4,// 3 + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,// 4 + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,// 5 + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,// 6 + 8, 8, 8, 8, 8, 8, 0, 8, 4, 4, 4, 4, 4, 4, 8, 4,// 7 + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,// 8 + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,// 9 + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,// A + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,// B + 8,12,16,16,12,16, 8,16, 8,16,16, 0,12,24, 8,16,// C + 8,12,16, 0,12,16, 8,16, 8,16,16, 0,12, 0, 8,16,// D + 12,12, 8, 0, 0,16, 8,16,16, 4,16, 0, 0, 0, 8,16,// E + 12,12, 8, 4, 0,16, 8,16,12, 8,16, 4, 0, 0, 8,16,// F + + // CB prefixed + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// 0 + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// 1 + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// 2 + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// 3 + 8, 8, 8, 8, 8, 8,12, 8, 8, 8, 8, 8, 8, 8,12, 8,// 4 + 8, 8, 8, 8, 8, 8,12, 8, 8, 8, 8, 8, 8, 8,12, 8,// 5 + 8, 8, 8, 8, 8, 8,12, 8, 8, 8, 8, 8, 8, 8,12, 8,// 6 + 8, 8, 8, 8, 8, 8,12, 8, 8, 8, 8, 8, 8, 8,12, 8,// 7 + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// 8 + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// 9 + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// A + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// B + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// C + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// D + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// E + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// F + }; + + if ( time >= 0 ) + goto stop; + + time += instr_times [op]; + + int data; + data = *instr; + s.time = time; + + #ifdef CPU_INSTR_HOOK + { CPU_INSTR_HOOK( (pc-1), (instr-1), rg.a, rp.bc, rp.de, rp.hl, sp ); } + #endif + + switch ( op ) + { + +// TODO: more efficient way to handle negative branch that wraps PC around +#define BRANCH_( cond, clocks )\ +{\ + pc++;\ + if ( !(cond) )\ + goto loop;\ + pc = WORD( pc + SBYTE( data ) );\ + time += clocks;\ + goto loop;\ +} + +#define BRANCH( cond ) BRANCH_( cond, 4 ) + +// Most Common + + case 0x20: // JR NZ + BRANCH( CC_NZ() ) + + case 0x21: // LD HL,IMM (common) + RP.hl = GET_ADDR(); + pc += 2; + goto loop; + + case 0x28: // JR Z + BRANCH( CC_Z() ) + + case 0xF2: // LD A,(0xFF00+C) + READ_IO( this, RG.c, RG.a ); + goto loop; + + case 0xF0: // LD A,(0xFF00+imm) + pc++; + READ_IO( this, data, RG.a ); + goto loop; + + { + int temp; + case 0x0A: // LD A,(BC) + temp = RP.bc; + goto ld_a_ind_comm; + + case 0x3A: // LD A,(HL-) + temp = RP.hl; + RP.hl = temp - 1; + goto ld_a_ind_comm; + + case 0x1A: // LD A,(DE) + temp = RP.de; + goto ld_a_ind_comm; + + case 0x2A: // LD A,(HL+) (common) + temp = RP.hl; + RP.hl = temp + 1; + goto ld_a_ind_comm; + + case 0xFA: // LD A,IND16 (common) + temp = GET_ADDR(); + pc += 2; + ld_a_ind_comm: + READ_FAST( this, temp, RG.a ); + goto loop; + } + + { + int temp; + case 0xBE: // CP (HL) + temp = READ_MEM( this, RP.hl ); + goto cmp_comm; + + case 0xB8: // CP B + case 0xB9: // CP C + case 0xBA: // CP D + case 0xBB: // CP E + case 0xBC: // CP H + case 0xBD: // CP L + case 0xBF: // CP A + temp = R8( op & 7 ); + cmp_comm: + ph = RG.a ^ temp; // N=1 H=* + cz = RG.a - temp; // C=* Z=* + goto loop; + } + + case 0xFE: // CP IMM + pc++; + ph = RG.a ^ data; // N=1 H=* + cz = RG.a - data; // C=* Z=* + goto loop; + + case 0x46: // LD B,(HL) + case 0x4E: // LD C,(HL) + case 0x56: // LD D,(HL) + case 0x5E: // LD E,(HL) + case 0x66: // LD H,(HL) + case 0x6E: // LD L,(HL) + case 0x7E:{// LD A,(HL) + int addr = RP.hl; + READ_FAST( this, addr, R8( op >> 3 & 7 ) ); + goto loop; + } + + case 0xC4: // CNZ (next-most-common) + pc += 2; + if ( CC_Z() ) + goto loop; + call: + time += 12; + pc -= 2; + case 0xCD: // CALL (most-common) + data = pc + 2; + pc = GET_ADDR(); + push: { + int addr = WORD( sp - 1 ); + WRITE_MEM( this, addr, (data >> 8) ); + sp = WORD( sp - 2 ); + WRITE_MEM( this, sp, data ); + goto loop; + } + + case 0xC8: // RET Z (next-most-common) + if ( CC_NZ() ) + goto loop; + ret: + time += 12; + case 0xD9: // RETI + case 0xC9:{// RET (most common) + pc = READ_MEM( this, sp ); + int addr = sp + 1; + sp = WORD( sp + 2 ); + pc += 0x100 * READ_MEM( this, addr ); + goto loop; + } + + case 0x00: // NOP + case 0x40: // LD B,B + case 0x49: // LD C,C + case 0x52: // LD D,D + case 0x5B: // LD E,E + case 0x64: // LD H,H + case 0x6D: // LD L,L + case 0x7F: // LD A,A + goto loop; + +// CB Instructions + + case 0xCB: + time += (instr_times + 256) [data]; + pc++; + // now data is the opcode + switch ( data ) { + + case 0x46: // BIT b,(HL) + case 0x4E: + case 0x56: + case 0x5E: + case 0x66: + case 0x6E: + case 0x76: + case 0x7E: { + int addr = RP.hl; + READ_FAST( this, addr, op ); + goto bit_comm; + } + + case 0x40: case 0x41: case 0x42: case 0x43: // BIT b,r + case 0x44: case 0x45: case 0x47: case 0x48: + case 0x49: case 0x4A: case 0x4B: case 0x4C: + case 0x4D: case 0x4F: case 0x50: case 0x51: + case 0x52: case 0x53: case 0x54: case 0x55: + case 0x57: case 0x58: case 0x59: case 0x5A: + case 0x5B: case 0x5C: case 0x5D: case 0x5F: + case 0x60: case 0x61: case 0x62: case 0x63: + case 0x64: case 0x65: case 0x67: case 0x68: + case 0x69: case 0x6A: case 0x6B: case 0x6C: + case 0x6D: case 0x6F: case 0x70: case 0x71: + case 0x72: case 0x73: case 0x74: case 0x75: + case 0x77: case 0x78: case 0x79: case 0x7A: + case 0x7B: case 0x7C: case 0x7D: case 0x7F: + op = R8( data & 7 ); + bit_comm: + ph = op >> (data >> 3 & 7) & 1; + cz = (cz & 0x100) + ph; + ph ^= 0x110; // N=0 H=1 + goto loop; + + case 0x86: // RES b,(HL) + case 0x8E: + case 0x96: + case 0x9E: + case 0xA6: + case 0xAE: + case 0xB6: + case 0xBE: { + int temp = READ_MEM( this, RP.hl ); + temp &= ~(1 << (data >> 3 & 7)); + WRITE_MEM( this, RP.hl, temp ); + goto loop; + } + + case 0xC6: // SET b,(HL) + case 0xCE: + case 0xD6: + case 0xDE: + case 0xE6: + case 0xEE: + case 0xF6: + case 0xFE: { + int temp = READ_MEM( this, RP.hl ); + temp |= 1 << (data >> 3 & 7); + WRITE_MEM( this, RP.hl, temp ); + goto loop; + } + + case 0xC0: case 0xC1: case 0xC2: case 0xC3: // SET b,r + case 0xC4: case 0xC5: case 0xC7: case 0xC8: + case 0xC9: case 0xCA: case 0xCB: case 0xCC: + case 0xCD: case 0xCF: case 0xD0: case 0xD1: + case 0xD2: case 0xD3: case 0xD4: case 0xD5: + case 0xD7: case 0xD8: case 0xD9: case 0xDA: + case 0xDB: case 0xDC: case 0xDD: case 0xDF: + case 0xE0: case 0xE1: case 0xE2: case 0xE3: + case 0xE4: case 0xE5: case 0xE7: case 0xE8: + case 0xE9: case 0xEA: case 0xEB: case 0xEC: + case 0xED: case 0xEF: case 0xF0: case 0xF1: + case 0xF2: case 0xF3: case 0xF4: case 0xF5: + case 0xF7: case 0xF8: case 0xF9: case 0xFA: + case 0xFB: case 0xFC: case 0xFD: case 0xFF: + R8( data & 7 ) |= 1 << (data >> 3 & 7); + goto loop; + + case 0x80: case 0x81: case 0x82: case 0x83: // RES b,r + case 0x84: case 0x85: case 0x87: case 0x88: + case 0x89: case 0x8A: case 0x8B: case 0x8C: + case 0x8D: case 0x8F: case 0x90: case 0x91: + case 0x92: case 0x93: case 0x94: case 0x95: + case 0x97: case 0x98: case 0x99: case 0x9A: + case 0x9B: case 0x9C: case 0x9D: case 0x9F: + case 0xA0: case 0xA1: case 0xA2: case 0xA3: + case 0xA4: case 0xA5: case 0xA7: case 0xA8: + case 0xA9: case 0xAA: case 0xAB: case 0xAC: + case 0xAD: case 0xAF: case 0xB0: case 0xB1: + case 0xB2: case 0xB3: case 0xB4: case 0xB5: + case 0xB7: case 0xB8: case 0xB9: case 0xBA: + case 0xBB: case 0xBC: case 0xBD: case 0xBF: + R8( data & 7 ) &= ~(1 << (data >> 3 & 7)); + goto loop; + + case 0x36: // SWAP (HL) + op = READ_MEM( this, RP.hl ); + goto swap_comm; + + case 0x30: // SWAP B + case 0x31: // SWAP C + case 0x32: // SWAP D + case 0x33: // SWAP E + case 0x34: // SWAP H + case 0x35: // SWAP L + case 0x37: // SWAP A + op = R8( data & 7 ); + swap_comm: + op = (op >> 4) + (op << 4); + cz = BYTE( op ); + ph = cz + 0x100; + if ( data == 0x36 ) + goto write_hl_op_ff; + R8( data & 7 ) = op; + goto loop; + +// Shift/Rotate + + case 0x26: // SLA (HL) + cz = 0; + case 0x16: // RL (HL) + cz = (cz >> 8 & 1) + (READ_MEM( this, RP.hl ) << 1); + goto rl_hl_common; + + case 0x06: // RLC (HL) + cz = READ_MEM( this, RP.hl ); + cz = (cz << 1) + (cz >> 7 & 1); + rl_hl_common: + // Z=* C=* + ph = cz | 0x100; // N=0 H=0 + WRITE_MEM( this, RP.hl, cz ); + goto loop; + + case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x27: // SLA r + cz = 0; + case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x17: // RL r + cz = (cz >> 8 & 1) + (R8( data & 7 ) << 1); + goto rl_common; + + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x07: // RLC r + cz = R8( data & 7 ); + cz = (cz << 1) + (cz >> 7 & 1); + rl_common: + // Z=* C=* + ph = cz | 0x100; // N=0 H=0 + R8( data & 7 ) = cz; + goto loop; + + case 0x0E: // RRC (HL) + cz = READ_MEM( this, RP.hl ); + cz += cz << 8 & 0x100; + goto rr_hl_common; + + case 0x2E: // SRA (HL) + cz = READ_MEM( this, RP.hl ); + cz += cz << 1 & 0x100; + goto rr_hl_common; + + case 0x3E: // SRL (HL) + cz = 0; + case 0x1E: // RR (HL) + cz = (cz & 0x100) + READ_MEM( this, RP.hl ); + rr_hl_common: + cz = (cz << 8) + (cz >> 1); // Z=* C=* + ph = cz | 0x100; // N=0 H=0 + WRITE_MEM( this, RP.hl, cz ); + goto loop; + + case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0F: // RRC r + cz = R8( data & 7 ); + cz += cz << 8 & 0x100; + goto rr_common; + + case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2F: // SRA r + cz = R8( data & 7 ); + cz += cz << 1 & 0x100; + goto rr_common; + + case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3F: // SRL r + cz = 0; + case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1F: // RR r + cz = (cz & 0x100) + R8( data & 7 ); + rr_common: + cz = (cz << 8) + (cz >> 1); // Z=* C=* + ph = cz | 0x100; // N=0 H=0 + R8( data & 7 ) = cz; + goto loop; + + } // CB op + assert( false ); // unhandled CB op + + case 0x07: // RLCA + cz = RG.a >> 7; + goto rlc_common; + case 0x17: // RLA + cz = cz >> 8 & 1; + rlc_common: + cz += RG.a << 1; + ph = cz | 0x100; + RG.a = BYTE( cz ); + cz |= 1; + goto loop; + + case 0x0F: // RRCA + ph = RG.a << 8; + goto rrc_common; + case 0x1F: // RRA + ph = cz; + rrc_common: + cz = (RG.a << 8) + 1; // Z=0 C=* + RG.a = ((ph & 0x100) + RG.a) >> 1; + ph = 0x100; // N=0 H=0 + goto loop; + +// Load + + case 0x70: // LD (HL),B + case 0x71: // LD (HL),C + case 0x72: // LD (HL),D + case 0x73: // LD (HL),E + case 0x74: // LD (HL),H + case 0x75: // LD (HL),L + case 0x77: // LD (HL),A + op = R8( op & 7 ); + write_hl_op_ff: + WRITE_MEM( this, RP.hl, op ); + goto loop; + + case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x47: // LD r,r + case 0x48: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4F: + case 0x50: case 0x51: case 0x53: case 0x54: case 0x55: case 0x57: + case 0x58: case 0x59: case 0x5A: case 0x5C: case 0x5D: case 0x5F: + case 0x60: case 0x61: case 0x62: case 0x63: case 0x65: case 0x67: + case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6F: + case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: + R8( op >> 3 & 7 ) = R8( op & 7 ); + goto loop; + + case 0x08: // LD IND16,SP + data = GET_ADDR(); + pc += 2; + WRITE_MEM( this, data, sp ); + data++; + WRITE_MEM( this, data, (sp >> 8) ); + goto loop; + + case 0xF9: // LD SP,HL + sp = RP.hl; + goto loop; + + case 0x31: // LD SP,IMM + sp = GET_ADDR(); + pc += 2; + goto loop; + + case 0x01: // LD BC,IMM + case 0x11: // LD DE,IMM + R16( (unsigned) op >> 4 ) = GET_ADDR(); + pc += 2; + goto loop; + + case 0xE2: // LD (0xFF00+C),A + WRITE_IO( this, RG.c, RG.a ); + goto loop; + + case 0xE0: // LD (0xFF00+imm),A + pc++; + WRITE_IO( this, data, RG.a ); + goto loop; + + { + int temp; + case 0x32: // LD (HL-),A + temp = RP.hl; + RP.hl = temp - 1; + goto write_data_rg_a; + + case 0x02: // LD (BC),A + temp = RP.bc; + goto write_data_rg_a; + + case 0x12: // LD (DE),A + temp = RP.de; + goto write_data_rg_a; + + case 0x22: // LD (HL+),A + temp = RP.hl; + RP.hl = temp + 1; + goto write_data_rg_a; + + case 0xEA: // LD IND16,A (common) + temp = GET_ADDR(); + pc += 2; + write_data_rg_a: + WRITE_MEM( this, temp, RG.a ); + goto loop; + } + + case 0x06: // LD B,IMM + RG.b = data; + pc++; + goto loop; + + case 0x0E: // LD C,IMM + RG.c = data; + pc++; + goto loop; + + case 0x16: // LD D,IMM + RG.d = data; + pc++; + goto loop; + + case 0x1E: // LD E,IMM + RG.e = data; + pc++; + goto loop; + + case 0x26: // LD H,IMM + RG.h = data; + pc++; + goto loop; + + case 0x2E: // LD L,IMM + RG.l = data; + pc++; + goto loop; + + case 0x36: // LD (HL),IMM + WRITE_MEM( this, RP.hl, data ); + pc++; + goto loop; + + case 0x3E: // LD A,IMM + RG.a = data; + pc++; + goto loop; + +// Increment/decrement + + case 0x03: // INC BC + case 0x13: // INC DE + case 0x23: // INC HL + R16( (unsigned) op >> 4 )++; + goto loop; + + case 0x33: // INC SP + sp = WORD( sp + 1 ); + goto loop; + + case 0x0B: // DEC BC + case 0x1B: // DEC DE + case 0x2B: // DEC HL + R16( (unsigned) op >> 4 )--; + goto loop; + + case 0x3B: // DEC SP + sp = WORD( sp - 1 ); + goto loop; + + case 0x34: // INC (HL) + op = RP.hl; + data = READ_MEM( this, op ); + data++; + WRITE_MEM( this, op, data ); + goto inc_comm; + + case 0x04: // INC B + case 0x0C: // INC C (common) + case 0x14: // INC D + case 0x1C: // INC E + case 0x24: // INC H + case 0x2C: // INC L + case 0x3C: // INC A + op = op >> 3 & 7; + data = R8( op ) + 1; + R8( op ) = data; + inc_comm: + ph = data - 0x101; // N=0 H=* + cz = (cz & 0x100) + BYTE( data ); // C=- Z=* + goto loop; + + case 0x35: // DEC (HL) + op = RP.hl; + data = READ_MEM( this, op ); + data--; + WRITE_MEM( this, op, data ); + goto dec_comm; + + case 0x05: // DEC B + case 0x0D: // DEC C + case 0x15: // DEC D + case 0x1D: // DEC E + case 0x25: // DEC H + case 0x2D: // DEC L + case 0x3D: // DEC A + op = op >> 3 & 7; + data = R8( op ) - 1; + R8( op ) = data; + dec_comm: + ph = data + 1; // N=1 H=* + cz = (cz & 0x100) + BYTE( data ); // C=- Z=* + goto loop; + +// Add 16-bit + + case 0xF8: // LD HL,SP+n + case 0xE8:{// ADD SP,n + pc++; + int t = WORD( sp + SBYTE( data ) ); + cz = ((BYTE( sp ) + data) & 0x100) + 1; // Z=0 C=* + ph = (sp ^ data ^ t) | 0x100; // N=0 H=* + if ( op == 0xF8 ) + { + RP.hl = t; + goto loop; + } + sp = t; + goto loop; + } + + case 0x39: // ADD HL,SP + data = sp; + goto add_hl_comm; + + case 0x09: // ADD HL,BC + case 0x19: // ADD HL,DE + case 0x29: // ADD HL,HL + data = R16( (unsigned) op >> 4 ); + add_hl_comm: + ph = RP.hl ^ data; + data += RP.hl; + RP.hl = WORD( data ); + ph ^= data; + cz = BYTE( cz ) + (data >> 8 & 0x100); // C=* Z=- + ph = ((ph >> 8) ^ cz) | 0x100; // N=0 H=* + goto loop; + + case 0x86: // ADD (HL) + data = READ_MEM( this, RP.hl ); + goto add_comm; + + case 0x80: // ADD B + case 0x81: // ADD C + case 0x82: // ADD D + case 0x83: // ADD E + case 0x84: // ADD H + case 0x85: // ADD L + case 0x87: // ADD A + data = R8( op & 7 ); + goto add_comm; + + case 0xC6: // ADD IMM + pc++; + add_comm: + ph = (RG.a ^ data) | 0x100; // N=1 H=* + cz = RG.a + data; // C=* Z=* + RG.a = cz; + goto loop; + +// Add/Subtract + + case 0x8E: // ADC (HL) + data = READ_MEM( this, RP.hl ); + goto adc_comm; + + case 0x88: // ADC B + case 0x89: // ADC C + case 0x8A: // ADC D + case 0x8B: // ADC E + case 0x8C: // ADC H + case 0x8D: // ADC L + case 0x8F: // ADC A + data = R8( op & 7 ); + goto adc_comm; + + case 0xCE: // ADC IMM + pc++; + adc_comm: + ph = (RG.a ^ data) | 0x100; // N=1 H=* + cz = RG.a + data + (cz >> 8 & 1); // C=* Z=* + RG.a = cz; + goto loop; + + case 0x96: // SUB (HL) + data = READ_MEM( this, RP.hl ); + goto sub_comm; + + case 0x90: // SUB B + case 0x91: // SUB C + case 0x92: // SUB D + case 0x93: // SUB E + case 0x94: // SUB H + case 0x95: // SUB L + case 0x97: // SUB A + data = R8( op & 7 ); + goto sub_comm; + + case 0xD6: // SUB IMM + pc++; + sub_comm: + ph = RG.a ^ data; // N=1 H=* + cz = RG.a - data; // C=* Z=* + RG.a = cz; + goto loop; + + case 0x9E: // SBC (HL) + data = READ_MEM( this, RP.hl ); + goto sbc_comm; + + case 0x98: // SBC B + case 0x99: // SBC C + case 0x9A: // SBC D + case 0x9B: // SBC E + case 0x9C: // SBC H + case 0x9D: // SBC L + case 0x9F: // SBC A + data = R8( op & 7 ); + goto sbc_comm; + + case 0xDE: // SBC IMM + pc++; + sbc_comm: + ph = RG.a ^ data; // N=1 H=* + cz = RG.a - data - (cz >> 8 & 1); // C=* Z=* + RG.a = cz; + goto loop; + +// Logical + + case 0xA0: // AND B + case 0xA1: // AND C + case 0xA2: // AND D + case 0xA3: // AND E + case 0xA4: // AND H + case 0xA5: // AND L + data = R8( op & 7 ); + goto and_comm; + + case 0xA6: // AND (HL) + data = READ_MEM( this, RP.hl ); + goto and_comm; + case 0xE6: // AND IMM + pc++; + and_comm: + cz = RG.a & data; // C=0 Z=* + ph = ~cz; // N=0 H=1 + RG.a = cz; + goto loop; + + case 0xA7: // AND A + cz = RG.a; // C=0 Z=* + ph = ~RG.a; // N=0 H=1 + goto loop; + + case 0xB0: // OR B + case 0xB1: // OR C + case 0xB2: // OR D + case 0xB3: // OR E + case 0xB4: // OR H + case 0xB5: // OR L + data = R8( op & 7 ); + goto or_comm; + + case 0xB6: // OR (HL) + data = READ_MEM( this, RP.hl ); + goto or_comm; + case 0xF6: // OR IMM + pc++; + or_comm: + cz = RG.a | data; // C=0 Z=* + ph = cz | 0x100; // N=0 H=0 + RG.a = cz; + goto loop; + + case 0xB7: // OR A + cz = RG.a; // C=0 Z=* + ph = RG.a + 0x100; // N=0 H=0 + goto loop; + + case 0xA8: // XOR B + case 0xA9: // XOR C + case 0xAA: // XOR D + case 0xAB: // XOR E + case 0xAC: // XOR H + case 0xAD: // XOR L + data = R8( op & 7 ); + goto xor_comm; + + case 0xAE: // XOR (HL) + data = READ_MEM( this, RP.hl ); + pc--; + case 0xEE: // XOR IMM + pc++; + xor_comm: + cz = RG.a ^ data; // C=0 Z=* + ph = cz + 0x100; // N=0 H=0 + RG.a = cz; + goto loop; + + case 0xAF: // XOR A + RG.a = 0; + cz = 0; // C=0 Z=* + ph = 0x100; // N=0 H=0 + goto loop; + +// Stack + + case 0xF1: // POP AF + case 0xC1: // POP BC + case 0xD1: // POP DE + case 0xE1: // POP HL (common) + data = READ_MEM( this, sp ); + R16( op >> 4 & 3 ) = data + 0x100 * READ_MEM( this, (sp + 1) ); + sp = WORD( sp + 2 ); + if ( op != 0xF1 ) + goto loop; + + SET_FLAGS( RG.a ); + RG.a = RG.flags; + goto loop; + + case 0xC5: // PUSH BC + data = RP.bc; + goto push; + + case 0xD5: // PUSH DE + data = RP.de; + goto push; + + case 0xE5: // PUSH HL + data = RP.hl; + goto push; + + case 0xF5: // PUSH AF + GET_FLAGS( data ); + data += RG.a << 8; + goto push; + +// Flow control + + case 0xFF: case 0xC7: case 0xCF: case 0xD7: // RST + case 0xDF: case 0xE7: case 0xEF: case 0xF7: + data = pc; + pc = (op & 0x38) + cpu->rst_base; + goto push; + + case 0xCC: // CALL Z + pc += 2; + if ( CC_Z() ) + goto call; + goto loop; + + case 0xD4: // CALL NC + pc += 2; + if ( CC_NC() ) + goto call; + goto loop; + + case 0xDC: // CALL C + pc += 2; + if ( CC_C() ) + goto call; + goto loop; + + case 0xC0: // RET NZ + if ( CC_NZ() ) + goto ret; + goto loop; + + case 0xD0: // RET NC + if ( CC_NC() ) + goto ret; + goto loop; + + case 0xD8: // RET C + if ( CC_C() ) + goto ret; + goto loop; + + case 0x18: // JR + BRANCH_( true, 0 ) + + case 0x30: // JR NC + BRANCH( CC_NC() ) + + case 0x38: // JR C + BRANCH( CC_C() ) + + case 0xE9: // LD PC,HL + pc = RP.hl; + goto loop; + + case 0xC3: // JP (next-most-common) + pc = GET_ADDR(); + goto loop; + + case 0xC2: // JP NZ + pc += 2; + if ( CC_NZ() ) + goto jp_taken; + time -= 4; + goto loop; + + case 0xCA: // JP Z (most common) + pc += 2; + if ( CC_Z() ) + goto jp_taken; + time -= 4; + goto loop; + + jp_taken: + pc -= 2; + pc = GET_ADDR(); + goto loop; + + case 0xD2: // JP NC + pc += 2; + if ( CC_NC() ) + goto jp_taken; + time -= 4; + goto loop; + + case 0xDA: // JP C + pc += 2; + if ( CC_C() ) + goto jp_taken; + time -= 4; + goto loop; + +// Flags + + case 0x2F: // CPL + RG.a = ~RG.a; + ph = BYTE( ~cz ); // N=1 H=1 + goto loop; + + case 0x3F: // CCF + ph = cz | 0x100; // N=0 H=0 + cz ^= 0x100; // C=* Z=- + goto loop; + + case 0x37: // SCF + ph = cz | 0x100; // N=0 H=0 + cz |= 0x100; // C=1 Z=- + goto loop; + + case 0xF3: // DI + goto loop; + + case 0xFB: // EI + goto loop; + + case 0x27:{// DAA + unsigned a = RG.a; + int h = ph ^ cz; + if ( ph & 0x100 ) + { + if ( (h & 0x10) || (a & 0x0F) > 9 ) + a += 6; + + if ( (cz & 0x100) || a > 0x9F ) + a += 0x60; + } + else + { + if ( h & 0x10 ) + a = (a - 6) & 0xFF; + + if ( cz & 0x100 ) + a -= 0x60; + } + cz = (cz & 0x100) | a; // C=- Z=* + RG.a = a; + ph = (ph & 0x100) + BYTE( a ); // N=- H=0 + goto loop; + } + +// Special + + case 0x76: // HALT + case 0x10: // STOP + case 0xD3: case 0xDB: case 0xDD: // Illegal + case 0xE3: case 0xE4: case 0xEB: case 0xEC: case 0xED: // (all freeze cpu) + case 0xF4: case 0xFC: case 0xFD: + goto stop; + } + + // If this fails then an opcode isn't handled above + assert( false ); + +stop: + pc--; + + // copy state back + cpu->cpu_state_.time = time; + cpu->r.pc = pc; + cpu->r.sp = sp; + { + int t; + GET_FLAGS( t ); + RG.flags = t; + } + cpu->cpu_state = &cpu->cpu_state_; + cpu->r.rp = RP; +} diff --git a/apps/codecs/libgme/gb_oscs.c b/apps/codecs/libgme/gb_oscs.c new file mode 100644 index 0000000000..6d607d7f5a --- /dev/null +++ b/apps/codecs/libgme/gb_oscs.c @@ -0,0 +1,787 @@ +// Gb_Snd_Emu 0.1.4. http://www.slack.net/~ant/ + +#include "gb_apu.h" + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const cgb_02 = 0; // enables bug in early CGB units that causes problems in some games +int const cgb_05 = 0; // enables CGB-05 zombie behavior + +int const trigger_mask = 0x80; +int const length_enabled = 0x40; + +void Osc_reset( struct Gb_Osc* this ) +{ + this->output = NULL; + this->last_amp = 0; + this->delay = 0; + this->phase = 0; + this->enabled = false; +} + +inline void Osc_update_amp( struct Gb_Osc* this, blip_time_t time, int new_amp ) +{ + Blip_set_modified( this->output ); + int delta = new_amp - this->last_amp; + if ( delta ) + { + this->last_amp = new_amp; + Synth_offset( this->synth, time, delta, this->output ); + } +} + +// Units + +void Osc_clock_length( struct Gb_Osc* this ) +{ + if ( (this->regs [4] & length_enabled) && this->length_ctr ) + { + if ( --this->length_ctr <= 0 ) + this->enabled = false; + } +} + +void Noise_clock_envelope( struct Gb_Noise* this ) +{ + if ( this->env_enabled && --this->env_delay <= 0 && Noise_reload_env_timer( this ) ) + { + int v = this->volume + (this->osc.regs [2] & 0x08 ? +1 : -1); + if ( 0 <= v && v <= 15 ) + this->volume = v; + else + this->env_enabled = false; + } +} + +void Square_clock_envelope( struct Gb_Square* this ) +{ + if ( this->env_enabled && --this->env_delay <= 0 && Square_reload_env_timer( this ) ) + { + int v = this->volume + (this->osc.regs [2] & 0x08 ? +1 : -1); + if ( 0 <= v && v <= 15 ) + this->volume = v; + else + this->env_enabled = false; + } +} + +inline void reload_sweep_timer( struct Gb_Square* this ) +{ + this->sweep_delay = (this->osc.regs [0] & period_mask) >> 4; + if ( !this->sweep_delay ) + this->sweep_delay = 8; +} + +void calc_sweep( struct Gb_Square* this, bool update ) +{ + struct Gb_Osc* osc = &this->osc; + int const shift = osc->regs [0] & shift_mask; + int const delta = this->sweep_freq >> shift; + this->sweep_neg = (osc->regs [0] & 0x08) != 0; + int const freq = this->sweep_freq + (this->sweep_neg ? -delta : delta); + + if ( freq > 0x7FF ) + { + osc->enabled = false; + } + else if ( shift && update ) + { + this->sweep_freq = freq; + + osc->regs [3] = freq & 0xFF; + osc->regs [4] = (osc->regs [4] & ~0x07) | (freq >> 8 & 0x07); + } +} + +void clock_sweep( struct Gb_Square* this ) +{ + if ( --this->sweep_delay <= 0 ) + { + reload_sweep_timer( this ); + if ( this->sweep_enabled && (this->osc.regs [0] & period_mask) ) + { + calc_sweep( this, true ); + calc_sweep( this, false ); + } + } +} + +int wave_access( struct Gb_Wave* this, int addr ) +{ + if ( this->osc.enabled ) + { + addr = this->osc.phase & (wave_bank_size - 1); + if ( this->osc.mode == mode_dmg ) + { + addr++; + if ( this->osc.delay > clk_mul ) + return -1; // can only access within narrow time window while playing + } + addr >>= 1; + } + return addr & 0x0F; +} + +// write_register + +int write_trig( struct Gb_Osc* this, int frame_phase, int max_len, int old_data ) +{ + int data = this->regs [4]; + + if ( (frame_phase & 1) && !(old_data & length_enabled) && this->length_ctr ) + { + if ( (data & length_enabled) || cgb_02 ) + this->length_ctr--; + } + + if ( data & trigger_mask ) + { + this->enabled = true; + if ( !this->length_ctr ) + { + this->length_ctr = max_len; + if ( (frame_phase & 1) && (data & length_enabled) ) + this->length_ctr--; + } + } + + if ( !this->length_ctr ) + this->enabled = false; + + return data & trigger_mask; +} + +inline void Noise_zombie_volume( struct Gb_Noise* this, int old, int data ) +{ + int v = this->volume; + if ( this->osc.mode == mode_agb || cgb_05 ) + { + // CGB-05 behavior, very close to AGB behavior as well + if ( (old ^ data) & 8 ) + { + if ( !(old & 8) ) + { + v++; + if ( old & 7 ) + v++; + } + + v = 16 - v; + } + else if ( (old & 0x0F) == 8 ) + { + v++; + } + } + else + { + // CGB-04&02 behavior, very close to MGB behavior as well + if ( !(old & 7) && this->env_enabled ) + v++; + else if ( !(old & 8) ) + v += 2; + + if ( (old ^ data) & 8 ) + v = 16 - v; + } + this->volume = v & 0x0F; +} + +inline void Square_zombie_volume( struct Gb_Square* this, int old, int data ) +{ + int v = this->volume; + if ( this->osc.mode == mode_agb || cgb_05 ) + { + // CGB-05 behavior, very close to AGB behavior as well + if ( (old ^ data) & 8 ) + { + if ( !(old & 8) ) + { + v++; + if ( old & 7 ) + v++; + } + + v = 16 - v; + } + else if ( (old & 0x0F) == 8 ) + { + v++; + } + } + else + { + // CGB-04&02 behavior, very close to MGB behavior as well + if ( !(old & 7) && this->env_enabled ) + v++; + else if ( !(old & 8) ) + v += 2; + + if ( (old ^ data) & 8 ) + v = 16 - v; + } + this->volume = v & 0x0F; +} + +bool Square_write_register( struct Gb_Square* this, int frame_phase, int reg, int old_data, int data ) +{ + int const max_len = 64; + + switch ( reg ) + { + case 1: + this->osc.length_ctr = max_len - (data & (max_len - 1)); + break; + + case 2: + if ( !Square_dac_enabled( this ) ) + this->osc.enabled = false; + + Square_zombie_volume( this, old_data, data ); + + if ( (data & 7) && this->env_delay == 8 ) + { + this->env_delay = 1; + Square_clock_envelope( this ); // TODO: really happens at next length clock + } + break; + + case 4: + if ( write_trig( &this->osc, frame_phase, max_len, old_data ) ) + { + this->volume = this->osc.regs [2] >> 4; + Square_reload_env_timer( this ); + this->env_enabled = true; + if ( frame_phase == 7 ) + this->env_delay++; + if ( !Square_dac_enabled( this ) ) + this->osc.enabled = false; + this->osc.delay = (this->osc.delay & (4 * clk_mul - 1)) + Square_period( this ); + return true; + } + } + + return false; +} + +inline void Noise_write_register( struct Gb_Noise* this, int frame_phase, int reg, int old_data, int data ) +{ + int const max_len = 64; + + switch ( reg ) + { + case 1: + this->osc.length_ctr = max_len - (data & (max_len - 1)); + break; + + case 2: + if ( !Noise_dac_enabled( this ) ) + this->osc.enabled = false; + + Noise_zombie_volume( this, old_data, data ); + + if ( (data & 7) && this->env_delay == 8 ) + { + this->env_delay = 1; + Noise_clock_envelope( this ); // TODO: really happens at next length clock + } + break; + + case 4: + if ( write_trig( &this->osc, frame_phase, max_len, old_data ) ) + { + this->volume = this->osc.regs [2] >> 4; + Noise_reload_env_timer( this ); + this->env_enabled = true; + if ( frame_phase == 7 ) + this->env_delay++; + if ( !Noise_dac_enabled( this ) ) + this->osc.enabled = false; + + this->osc.phase = 0x7FFF; + this->osc.delay += 8 * clk_mul; + } + } +} + +inline void Sweep_write_register( struct Gb_Square* this, int frame_phase, int reg, int old_data, int data ) +{ + if ( reg == 0 && this->sweep_enabled && this->sweep_neg && !(data & 0x08) ) + this->osc.enabled = false; // sweep negate disabled after used + + if ( Square_write_register( this, frame_phase, reg, old_data, data ) ) + { + this->sweep_freq = Osc_frequency( &this->osc ); + this->sweep_neg = false; + reload_sweep_timer( this ); + this->sweep_enabled = (this->osc.regs [0] & (period_mask | shift_mask)) != 0; + if ( this->osc.regs [0] & shift_mask ) + calc_sweep( this, false ); + } +} + +void corrupt_wave( struct Gb_Wave* this ) +{ + int pos = ((this->osc.phase + 1) & (wave_bank_size - 1)) >> 1; + if ( pos < 4 ) + this->wave_ram [0] = this->wave_ram [pos]; + else { + int i; + for ( i = 4; --i >= 0; ) + this->wave_ram [i] = this->wave_ram [(pos & ~3) + i]; + } +} + +inline void Wave_write_register( struct Gb_Wave* this, int frame_phase, int reg, int old_data, int data ) +{ + int const max_len = 256; + + switch ( reg ) + { + case 0: + if ( !Wave_dac_enabled( this ) ) + this->osc.enabled = false; + break; + + case 1: + this->osc.length_ctr = max_len - data; + break; + + case 4: + { + bool was_enabled = this->osc.enabled; + if ( write_trig( &this->osc, frame_phase, max_len, old_data ) ) + { + if ( !Wave_dac_enabled( this ) ) + this->osc.enabled = false; + else if ( this->osc.mode == mode_dmg && was_enabled && + (unsigned) (this->osc.delay - 2 * clk_mul) < 2 * clk_mul ) + corrupt_wave( this ); + + this->osc.phase = 0; + this->osc.delay = Wave_period( this ) + 6 * clk_mul; + } + } + } +} + +void write_osc( struct Gb_Apu* this, int reg, int old_data, int data ) +{ + int index = (reg * 3 + 3) >> 4; // avoids divide + assert( index == reg / 5 ); + reg -= index * 5; + switch ( index ) + { + case 0: Sweep_write_register ( &this->square1, this->frame_phase, reg, old_data, data ); break; + case 1: Square_write_register( &this->square2, this->frame_phase, reg, old_data, data ); break; + case 2: Wave_write_register ( &this->wave, this->frame_phase, reg, old_data, data ); break; + case 3: Noise_write_register ( &this->noise, this->frame_phase, reg, old_data, data ); break; + } +} + +// Synthesis + +void Square_run( struct Gb_Square* this, blip_time_t time, blip_time_t end_time ) +{ + // Calc duty and phase + static byte const duty_offsets [4] ICONST_ATTR = { 1, 1, 3, 7 }; + static byte const duties [4] ICONST_ATTR = { 1, 2, 4, 6 }; + + struct Gb_Osc* osc = &this->osc; + int const duty_code = osc->regs [1] >> 6; + int duty_offset = duty_offsets [duty_code]; + int duty = duties [duty_code]; + if ( osc->mode == mode_agb ) + { + // AGB uses inverted duty + duty_offset -= duty; + duty = 8 - duty; + } + int ph = (osc->phase + duty_offset) & 7; + + // Determine what will be generated + int vol = 0; + struct Blip_Buffer* const out = osc->output; + if ( out ) + { + int amp = osc->dac_off_amp; + if ( Square_dac_enabled( this ) ) + { + if ( osc->enabled ) + vol = this->volume; + + amp = -dac_bias; + if ( osc->mode == mode_agb ) + amp = -(vol >> 1); + + // Play inaudible frequencies as constant amplitude + if ( Osc_frequency( osc ) >= 0x7FA && osc->delay < 32 * clk_mul ) + { + amp += (vol * duty) >> 3; + vol = 0; + } + + if ( ph < duty ) + { + amp += vol; + vol = -vol; + } + } + Osc_update_amp( osc, time, amp ); + } + + // Generate wave + time += osc->delay; + if ( time < end_time ) + { + int const per = Square_period( this ); + if ( !vol ) + { + #ifdef GB_APU_FAST + time = end_time; + #else + // Maintain phase when not playing + int count = (end_time - time + per - 1) / per; + ph += count; // will be masked below + time += (blip_time_t) count * per; + #endif + } + else + { + // Output amplitude transitions + int delta = vol; + do + { + ph = (ph + 1) & 7; + if ( ph == 0 || ph == duty ) + { + Synth_offset_inline( osc->synth, time, delta, out ); + delta = -delta; + } + time += per; + } + while ( time < end_time ); + + if ( delta != vol ) + osc->last_amp -= delta; + } + osc->phase = (ph - duty_offset) & 7; + } + osc->delay = time - end_time; +} + +#ifndef GB_APU_FAST +// Quickly runs LFSR for a large number of clocks. For use when noise is generating +// no sound. +static unsigned run_lfsr( unsigned s, unsigned mask, int count ) +{ + bool const optimized = true; // set to false to use only unoptimized loop in middle + + // optimization used in several places: + // ((s & (1 << b)) << n) ^ ((s & (1 << b)) << (n + 1)) = (s & (1 << b)) * (3 << n) + + if ( mask == 0x4000 && optimized ) + { + if ( count >= 32767 ) + count %= 32767; + + // Convert from Fibonacci to Galois configuration, + // shifted left 1 bit + s ^= (s & 1) * 0x8000; + + // Each iteration is equivalent to clocking LFSR 255 times + while ( (count -= 255) > 0 ) + s ^= ((s & 0xE) << 12) ^ ((s & 0xE) << 11) ^ (s >> 3); + count += 255; + + // Each iteration is equivalent to clocking LFSR 15 times + // (interesting similarity to single clocking below) + while ( (count -= 15) > 0 ) + s ^= ((s & 2) * (3 << 13)) ^ (s >> 1); + count += 15; + + // Remaining singles + while ( --count >= 0 ) + s = ((s & 2) * (3 << 13)) ^ (s >> 1); + + // Convert back to Fibonacci configuration + s &= 0x7FFF; + } + else if ( count < 8 || !optimized ) + { + // won't fully replace upper 8 bits, so have to do the unoptimized way + while ( --count >= 0 ) + s = (s >> 1 | mask) ^ (mask & -((s - 1) & 2)); + } + else + { + if ( count > 127 ) + { + count %= 127; + if ( !count ) + count = 127; // must run at least once + } + + // Need to keep one extra bit of history + s = s << 1 & 0xFF; + + // Convert from Fibonacci to Galois configuration, + // shifted left 2 bits + s ^= (s & 2) * 0x80; + + // Each iteration is equivalent to clocking LFSR 7 times + // (interesting similarity to single clocking below) + while ( (count -= 7) > 0 ) + s ^= ((s & 4) * (3 << 5)) ^ (s >> 1); + count += 7; + + // Remaining singles + while ( --count >= 0 ) + s = ((s & 4) * (3 << 5)) ^ (s >> 1); + + // Convert back to Fibonacci configuration and + // repeat last 8 bits above significant 7 + s = (s << 7 & 0x7F80) | (s >> 1 & 0x7F); + } + + return s; +} +#endif + +void Noise_run( struct Gb_Noise* this, blip_time_t time, blip_time_t end_time ) +{ + // Determine what will be generated + int vol = 0; + struct Gb_Osc* osc = &this->osc; + struct Blip_Buffer* const out = osc->output; + if ( out ) + { + int amp = osc->dac_off_amp; + if ( Noise_dac_enabled( this ) ) + { + if ( osc->enabled ) + vol = this->volume; + + amp = -dac_bias; + if ( osc->mode == mode_agb ) + amp = -(vol >> 1); + + if ( !(osc->phase & 1) ) + { + amp += vol; + vol = -vol; + } + } + + // AGB negates final output + if ( osc->mode == mode_agb ) + { + vol = -vol; + amp = -amp; + } + + Osc_update_amp( osc, time, amp ); + } + + // Run timer and calculate time of next LFSR clock + static byte const period1s [8] ICONST_ATTR = { 1, 2, 4, 6, 8, 10, 12, 14 }; + int const period1 = period1s [osc->regs [3] & 7] * clk_mul; + + #ifdef GB_APU_FAST + time += delay; + #else + { + int extra = (end_time - time) - osc->delay; + int const per2 = period2( this, 8 ); + time += osc->delay + ((this->divider ^ (per2 >> 1)) & (per2 - 1)) * period1; + + int count = (extra < 0 ? 0 : (extra + period1 - 1) / period1); + this->divider = (this->divider - count) & period2_mask; + osc->delay = count * period1 - extra; + } + #endif + + // Generate wave + if ( time < end_time ) + { + unsigned const mask = lfsr_mask( this ); + unsigned bits = osc->phase; + + int per = period2( this, period1 * 8 ); + #ifdef GB_APU_FAST + // Noise can be THE biggest time hog; adjust as necessary + int const min_period = 24; + if ( per < min_period ) + per = min_period; + #endif + if ( period2_index( this ) >= 0xE ) + { + time = end_time; + } + else if ( !vol ) + { + #ifdef GB_APU_FAST + time = end_time; + #else + // Maintain phase when not playing + int count = (end_time - time + per - 1) / per; + time += (blip_time_t) count * per; + bits = run_lfsr( bits, ~mask, count ); + #endif + } + else + { + struct Blip_Synth* synth = osc->synth; // cache + + // Output amplitude transitions + int delta = -vol; + do + { + unsigned changed = bits + 1; + bits = bits >> 1 & mask; + if ( changed & 2 ) + { + bits |= ~mask; + delta = -delta; + Synth_offset_inline( synth, time, delta, out ); + } + time += per; + } + while ( time < end_time ); + + if ( delta == vol ) + osc->last_amp += delta; + } + osc->phase = bits; + } + + #ifdef GB_APU_FAST + osc->delay = time - end_time; + #endif +} + +void Wave_run( struct Gb_Wave* this, blip_time_t time, blip_time_t end_time ) +{ + // Calc volume +#ifdef GB_APU_NO_AGB + static byte const shifts [4] = { 4+4, 0+4, 1+4, 2+4 }; + int const volume_idx = this->regs [2] >> 5 & 3; + int const volume_shift = shifts [volume_idx]; + int const volume_mul = 1; +#else + static byte const volumes [8] ICONST_ATTR = { 0, 4, 2, 1, 3, 3, 3, 3 }; + int const volume_shift = 2 + 4; + int const volume_idx = this->osc.regs [2] >> 5 & (this->agb_mask | 3); // 2 bits on DMG/CGB, 3 on AGB + int const volume_mul = volumes [volume_idx]; +#endif + + // Determine what will be generated + int playing = false; + struct Gb_Osc* osc = &this->osc; + struct Blip_Buffer* out = osc->output; + if ( out ) + { + int amp = osc->dac_off_amp; + if ( Wave_dac_enabled( this ) ) + { + // Play inaudible frequencies as constant amplitude + amp = 8 << 4; // really depends on average of all samples in wave + + // if delay is larger, constant amplitude won't start yet + if ( Osc_frequency( osc ) <= 0x7FB || osc->delay > 15 * clk_mul ) + { + if ( volume_mul && volume_shift != 4+4 ) + playing = (int) osc->enabled; + + amp = (this->sample_buf << (osc->phase << 2 & 4) & 0xF0) * playing; + } + + amp = ((amp * volume_mul) >> volume_shift) - dac_bias; + } + Osc_update_amp( osc, time, amp ); + } + + // Generate wave + time += osc->delay; + if ( time < end_time ) + { + byte const* wave = this->wave_ram; + + // wave size and bank + #ifdef GB_APU_NO_AGB + int const wave_mask = 0x1F; + int const swap_banks = 0; + #else + int const size20_mask = 0x20; + int const flags = osc->regs [0] & this->agb_mask; + int const wave_mask = (flags & size20_mask) | 0x1F; + int swap_banks = 0; + if ( flags & bank40_mask ) + { + swap_banks = flags & size20_mask; + wave += wave_bank_size/2 - (swap_banks >> 1); + } + #endif + + int ph = osc->phase ^ swap_banks; + ph = (ph + 1) & wave_mask; // pre-advance + + int const per = Wave_period( this ); + if ( !playing ) + { + #ifdef GB_APU_FAST + time = end_time; + #else + // Maintain phase when not playing + int count = (end_time - time + per - 1) / per; + ph += count; // will be masked below + time += (blip_time_t) count * per; + #endif + } + else + { + struct Blip_Synth* synth = osc->synth; // cache + + // Output amplitude transitions + int lamp = osc->last_amp + dac_bias; + do + { + // Extract nibble + int nibble = wave [ph >> 1] << (ph << 2 & 4) & 0xF0; + ph = (ph + 1) & wave_mask; + + // Scale by volume + int amp = (nibble * volume_mul) >> volume_shift; + + int delta = amp - lamp; + if ( delta ) + { + lamp = amp; + Synth_offset_inline( synth, time, delta, out ); + } + time += per; + } + while ( time < end_time ); + osc->last_amp = lamp - dac_bias; + } + ph = (ph - 1) & wave_mask; // undo pre-advance and mask position + + // Keep track of last byte read + if ( osc->enabled ) + this->sample_buf = wave [ph >> 1]; + + osc->phase = ph ^ swap_banks; // undo swapped banks + } + osc->delay = time - end_time; +} diff --git a/apps/codecs/libgme/gb_oscs.h b/apps/codecs/libgme/gb_oscs.h new file mode 100644 index 0000000000..0cc9d3f567 --- /dev/null +++ b/apps/codecs/libgme/gb_oscs.h @@ -0,0 +1,198 @@ +// Private oscillators used by Gb_Apu + +// Gb_Snd_Emu 0.1.4 +#ifndef GB_OSCS_H +#define GB_OSCS_H + +#include "blargg_common.h" +#include "blip_buffer.h" + +#ifndef GB_APU_OVERCLOCK + #define GB_APU_OVERCLOCK 1 +#endif + +#if GB_APU_OVERCLOCK & (GB_APU_OVERCLOCK - 1) + #error "GB_APU_OVERCLOCK must be a power of 2" +#endif + +enum { clk_mul = GB_APU_OVERCLOCK }; +enum { dac_bias = 7 }; + +struct Gb_Osc { + struct Blip_Buffer* outputs [4];// NULL, right, left, center + struct Blip_Buffer* output; // where to output sound + uint8_t* regs; // osc's 5 registers + int mode; // mode_dmg, mode_cgb, mode_agb + int dac_off_amp;// amplitude when DAC is off + int last_amp; // current amplitude in Blip_Buffer + + struct Blip_Synth* synth; + + int delay; // clocks until frequency timer expires + int length_ctr; // length counter + unsigned phase; // waveform phase (or equivalent) + bool enabled; // internal enabled flag +}; + +// 11-bit frequency in NRx3 and NRx4 +static inline int Osc_frequency( struct Gb_Osc* this ) { return (this->regs [4] & 7) * 0x100 + this->regs [3]; } + +void Osc_update_amp( struct Gb_Osc* this, blip_time_t, int new_amp ) ICODE_ATTR; +int Osc_write_trig( struct Gb_Osc* this, int frame_phase, int max_len, int old_data ) ICODE_ATTR; +void Osc_clock_length( struct Gb_Osc* this ) ICODE_ATTR; +void Osc_reset( struct Gb_Osc* this ); + +// Square + +enum { period_mask = 0x70 }; +enum { shift_mask = 0x07 }; + +struct Gb_Square { + struct Gb_Osc osc; + + int env_delay; + int volume; + bool env_enabled; + + // Sweep square + int sweep_freq; + int sweep_delay; + bool sweep_enabled; + bool sweep_neg; +}; + +bool Square_write_register( struct Gb_Square* this, int frame_phase, int reg, int old_data, int data ) ICODE_ATTR; +void Square_run( struct Gb_Square* this, blip_time_t, blip_time_t ) ICODE_ATTR; +void Square_clock_envelope( struct Gb_Square* this ) ICODE_ATTR; + +static inline void Square_reset( struct Gb_Square* this ) +{ + this->env_delay = 0; + this->volume = 0; + Osc_reset( &this->osc ); + this->osc.delay = 0x40000000; // TODO: something less hacky (never clocked until first trigger) +} +// Frequency timer period +static inline int Square_period( struct Gb_Square* this ) { return (2048 - Osc_frequency( &this->osc )) * (4 * clk_mul); } +static inline int Square_dac_enabled( struct Gb_Square* this) { return this->osc.regs [2] & 0xF8; } +static inline int Square_reload_env_timer( struct Gb_Square* this ) +{ + int raw = this->osc.regs [2] & 7; + this->env_delay = (raw ? raw : 8); + return raw; +} + +// Sweep square + +void clock_sweep( struct Gb_Square* this ) ICODE_ATTR; +void Sweep_write_register( struct Gb_Square* this, int frame_phase, int reg, int old_data, int data ) ICODE_ATTR; + +static inline void Sweep_reset( struct Gb_Square* this ) +{ + this->sweep_freq = 0; + this->sweep_delay = 0; + this->sweep_enabled = false; + this->sweep_neg = false; + + this->env_delay = 0; + this->volume = 0; + Osc_reset( &this->osc ); + this->osc.delay = 0x40000000; // TODO: something less hacky (never clocked until first trigger) +} + +void calc_sweep( struct Gb_Square* this, bool update ) ICODE_ATTR; +void reload_sweep_timer( struct Gb_Square* this ) ICODE_ATTR; + +// Noise + +enum { period2_mask = 0x1FFFF }; + +struct Gb_Noise { + struct Gb_Osc osc; + + int env_delay; + int volume; + bool env_enabled; + + int divider; // noise has more complex frequency divider setup +}; + +void Noise_run( struct Gb_Noise* this, blip_time_t, blip_time_t ) ICODE_ATTR; +void Noise_write_register( struct Gb_Noise* this, int frame_phase, int reg, int old_data, int data ) ICODE_ATTR; + +static inline void Noise_reset( struct Gb_Noise* this ) +{ + this->divider = 0; + + this->env_delay = 0; + this->volume = 0; + Osc_reset( &this->osc ); + this->osc.delay = 4 * clk_mul; // TODO: remove? +} + +void Noise_clock_envelope( struct Gb_Noise* this ) ICODE_ATTR; + +// Non-zero if DAC is enabled +static inline int Noise_dac_enabled( struct Gb_Noise* this) { return this->osc.regs [2] & 0xF8; } +static inline int Noise_reload_env_timer( struct Gb_Noise* this ) +{ + int raw = this->osc.regs [2] & 7; + this->env_delay = (raw ? raw : 8); + return raw; +} + +static inline int period2_index( struct Gb_Noise* this ) { return this->osc.regs [3] >> 4; } +static inline int period2( struct Gb_Noise* this, int base ) { return base << period2_index( this ); } +static inline unsigned lfsr_mask( struct Gb_Noise* this ) { return (this->osc.regs [3] & 0x08) ? ~0x4040 : ~0x4000; } + +// Wave + +enum { bank40_mask = 0x40 }; +enum { wave_bank_size = 32 }; + +struct Gb_Wave { + struct Gb_Osc osc; + + int sample_buf; // last wave RAM byte read (hardware has this as well) + + int agb_mask; // 0xFF if AGB features enabled, 0 otherwise + uint8_t* wave_ram; // 32 bytes (64 nybbles), stored in APU +}; + +void Wave_write_register( struct Gb_Wave* this, int frame_phase, int reg, int old_data, int data ) ICODE_ATTR; +void Wave_run( struct Gb_Wave* this, blip_time_t, blip_time_t ) ICODE_ATTR; + +static inline void Wave_reset( struct Gb_Wave* this ) +{ + this->sample_buf = 0; + Osc_reset( &this->osc ); +} + +// Frequency timer period +static inline int Wave_period( struct Gb_Wave* this ) { return (2048 - Osc_frequency( &this->osc )) * (2 * clk_mul); } + +// Non-zero if DAC is enabled +static inline int Wave_dac_enabled( struct Gb_Wave* this ) { return this->osc.regs [0] & 0x80; } + +void corrupt_wave( struct Gb_Wave* this ); + +static inline uint8_t* wave_bank( struct Gb_Wave* this ) { return &this->wave_ram [(~this->osc.regs [0] & bank40_mask) >> 2 & this->agb_mask]; } + +// Wave index that would be accessed, or -1 if no access would occur +int wave_access( struct Gb_Wave* this, int addr ) ICODE_ATTR; + +// Reads/writes wave RAM +static inline int Wave_read( struct Gb_Wave* this, int addr ) +{ + int index = wave_access( this, addr ); + return (index < 0 ? 0xFF : wave_bank( this ) [index]); +} + +static inline void Wave_write( struct Gb_Wave* this, int addr, int data ) +{ + int index = wave_access( this, addr ); + if ( index >= 0 ) + wave_bank( this ) [index] = data;; +} + +#endif diff --git a/apps/codecs/libgme/gbs_cpu.c b/apps/codecs/libgme/gbs_cpu.c new file mode 100644 index 0000000000..5a27bf6abe --- /dev/null +++ b/apps/codecs/libgme/gbs_cpu.c @@ -0,0 +1,120 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "gbs_emu.h" +#include "blargg_endian.h" + +/* Copyright (C) 2003-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#ifndef LOG_MEM + #define LOG_MEM( addr, str, data ) data +#endif + +int Read_mem( struct Gbs_Emu* this, addr_t addr ) +{ + int result = *Cpu_get_code( &this->cpu, addr ); + if ( (unsigned) (addr - io_addr) < io_size ) + result = Apu_read_register( &this->apu, Time( this ), addr ); + + return LOG_MEM( addr, ">", result ); +} + +inline void Write_io_inline( struct Gbs_Emu* this, int offset, int data, int base ) +{ + if ( (unsigned) (offset - (io_addr - base)) < io_size ) + Apu_write_register( &this->apu, Time( this ), offset + base, data & 0xFF ); + else if ( (unsigned) (offset - (0xFF06 - base)) < 2 ) + Update_timer( this ); + else if ( offset == io_base - base ) + this->ram [base - ram_addr + offset] = 0; // keep joypad return value 0 + else + this->ram [base - ram_addr + offset] = 0xFF; +} + +void Write_mem( struct Gbs_Emu* this, addr_t addr, int data ) +{ + (void) LOG_MEM( addr, "<", data ); + + int offset = addr - ram_addr; + if ( (unsigned) offset < 0x10000 - ram_addr ) + { + this->ram [offset] = data; + + offset -= 0xE000 - ram_addr; + if ( (unsigned) offset < 0x1F80 ) + Write_io_inline( this, offset, data, 0xE000 ); + } + else if ( (unsigned) (offset - (0x2000 - ram_addr)) < 0x2000 ) + { + Set_bank( this, data & 0xFF ); + } +#ifndef NDEBUG + else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 ) + { + /* dprintf( "Unmapped write $%04X\n", (unsigned) addr ); */ + } +#endif +} + +void Write_io_( struct Gbs_Emu* this, int offset, int data ) +{ + Write_io_inline( this, offset, data, io_base ); +} + +inline void Write_io( struct Gbs_Emu* this, int offset, int data ) +{ + (void) LOG_MEM( offset + io_base, "<", data ); + + this->ram [io_base - ram_addr + offset] = data; + if ( (unsigned) offset < 0x80 ) + Write_io_( this, offset, data ); +} + +int Read_io( struct Gbs_Emu* this, int offset ) +{ + int const io_base = 0xFF00; + int result = this->ram [io_base - ram_addr + offset]; + + if ( (unsigned) (offset - (io_addr - io_base)) < io_size ) + { + result = Apu_read_register( &this->apu, Time( this ), offset + io_base ); + (void) LOG_MEM( offset + io_base, ">", result ); + } + else + { + check( result == read_mem( offset + io_base ) ); + } + return result; +} + +#define READ_FAST( emu, addr, out ) \ +{\ + out = READ_CODE( addr );\ + if ( (unsigned) (addr - io_addr) < io_size )\ + out = LOG_MEM( addr, ">", Apu_read_register( &emu->apu, TIME() + emu->end_time, addr ) );\ + else\ + check( out == Read_mem( emu, addr ) );\ +} + +#define READ_MEM( emu, addr ) Read_mem( emu, addr ) +#define WRITE_MEM( emu, addr, data ) Write_mem( emu, addr, data ) + +#define WRITE_IO( emu, addr, data ) Write_io( emu, addr, data ) +#define READ_IO( emu, addr, out ) out = Read_io( emu, addr ) + +#define CPU_BEGIN \ +void Run_cpu( struct Gbs_Emu* this )\ +{ \ + struct Gb_Cpu* cpu = &this->cpu; + #include "gb_cpu_run.h" +} diff --git a/apps/codecs/libgme/gbs_emu.c b/apps/codecs/libgme/gbs_emu.c new file mode 100644 index 0000000000..693e84a39b --- /dev/null +++ b/apps/codecs/libgme/gbs_emu.c @@ -0,0 +1,631 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "gbs_emu.h" + +#include "blargg_endian.h" +#include "blargg_source.h" + +/* Copyright (C) 2003-2006 Shay Green. this module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. this +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + + +const char gme_wrong_file_type [] ICONST_ATTR = "Wrong file type for this emulator"; + +int const idle_addr = 0xF00D; +int const tempo_unit = 16; + +int const stereo = 2; // number of channels for stereo +int const silence_max = 6; // seconds +int const silence_threshold = 0x10; +long const fade_block_size = 512; +int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) + +void clear_track_vars( struct Gbs_Emu* this ) +{ + this->current_track_ = -1; + this->out_time = 0; + this->emu_time = 0; + this->emu_track_ended_ = true; + this->track_ended = true; + this->fade_start = LONG_MAX / 2 + 1; + this->fade_step = 1; + this->silence_time = 0; + this->silence_count = 0; + this->buf_remain = 0; +} + +void Gbs_init( struct Gbs_Emu* this ) +{ + this->sample_rate_ = 0; + this->mute_mask_ = 0; + this->tempo_ = 1.0; + + // Unload + this->header.timer_mode = 0; + clear_track_vars( this ); + + this->ignore_silence = false; + this->silence_lookahead = 6; + this->max_initial_silence = 21; + Sound_set_gain( this, 1.2 ); + + Rom_init( &this->rom, 0x4000 ); + + Apu_init( &this->apu ); + Cpu_init( &this->cpu ); + + this->tempo = tempo_unit; + this->sound_hardware = sound_gbs; + + // Reduce apu sound clicks? + Apu_reduce_clicks( &this->apu, true ); +} + +static blargg_err_t check_gbs_header( void const* header ) +{ + if ( memcmp( header, "GBS", 3 ) ) + return gme_wrong_file_type; + return 0; +} + +// Setup + +blargg_err_t Gbs_load( struct Gbs_Emu* this, void* data, long size ) +{ + // Unload + this->header.timer_mode = 0; + this->voice_count_ = 0; + this->m3u.size = 0; + clear_track_vars( this ); + + assert( offsetof (struct header_t,copyright [32]) == header_size ); + RETURN_ERR( Rom_load( &this->rom, data, size, header_size, &this->header, 0 ) ); + + RETURN_ERR( check_gbs_header( &this->header ) ); + + /* Ignore warnings? */ + /*if ( header_.vers != 1 ) + warning( "Unknown file version" ); + + if ( header_.timer_mode & 0x78 ) + warning( "Invalid timer mode" ); */ + + /* unsigned load_addr = get_le16( this->header.load_addr ); */ + /* if ( (header_.load_addr [1] | header_.init_addr [1] | header_.play_addr [1]) > 0x7F || + load_addr < 0x400 ) + warning( "Invalid load/init/play address" ); */ + + unsigned load_addr = get_le16( this->header.load_addr ); + /* if ( (this->header.load_addr [1] | this->header.init_addr [1] | this->header.play_addr [1]) > 0x7F || + load_addr < 0x400 ) + warning( "Invalid load/init/play address" ); */ + + this->cpu.rst_base = load_addr; + Rom_set_addr( &this->rom, load_addr ); + + this->voice_count_ = osc_count; + Apu_volume( &this->apu, this->gain_ ); + + // Change clock rate & setup buffer + this->clock_rate_ = 4194304; + Buffer_clock_rate( &this->stereo_buf, 4194304 ); + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); + + // Post load + Sound_set_tempo( this, this->tempo_ ); + + // Remute voices + Sound_mute_voices( this, this->mute_mask_ ); + + // Reset track count + this->track_count = this->header.track_count; + return 0; +} + +// Emulation + +// see gb_cpu_io.h for read/write functions + +void Set_bank( struct Gbs_Emu* this, int n ) +{ + addr_t addr = mask_addr( n * this->rom.bank_size, this->rom.mask ); + if ( addr == 0 && this->rom.size > this->rom.bank_size ) + addr = this->rom.bank_size; // MBC1&2 behavior, bank 0 acts like bank 1 + Cpu_map_code( &this->cpu, this->rom.bank_size, this->rom.bank_size, Rom_at_addr( &this->rom, addr ) ); +} + +void Update_timer( struct Gbs_Emu* this ) +{ + this->play_period = 70224 / tempo_unit; /// 59.73 Hz + + if ( this->header.timer_mode & 0x04 ) + { + // Using custom rate + static byte const rates [4] = { 6, 0, 2, 4 }; + // TODO: emulate double speed CPU mode rather than halving timer rate + int double_speed = this->header.timer_mode >> 7; + int shift = rates [this->ram [hi_page + 7] & 3] - double_speed; + this->play_period = (256 - this->ram [hi_page + 6]) << shift; + } + + this->play_period *= this->tempo; +} + +// Jumps to routine, given pointer to address in file header. Pushes idle_addr +// as return address, NOT old PC. +void Jsr_then_stop( struct Gbs_Emu* this, byte const addr [] ) +{ + check( this->cpu.r.sp == get_le16( this->header.stack_ptr ) ); + this->cpu.r.pc = get_le16( addr ); + Write_mem( this, --this->cpu.r.sp, idle_addr >> 8 ); + Write_mem( this, --this->cpu.r.sp, idle_addr ); +} + +blargg_err_t Run_until( struct Gbs_Emu* this, int end ) +{ + this->end_time = end; + Cpu_set_time( &this->cpu, Cpu_time( &this->cpu ) - end ); + while ( true ) + { + Run_cpu( this ); + if ( Cpu_time( &this->cpu ) >= 0 ) + break; + + if ( this->cpu.r.pc == idle_addr ) + { + if ( this->next_play > this->end_time ) + { + Cpu_set_time( &this->cpu, 0 ); + break; + } + + if ( Cpu_time( &this->cpu ) < this->next_play - this->end_time ) + Cpu_set_time( &this->cpu, this->next_play - this->end_time ); + this->next_play += this->play_period; + Jsr_then_stop( this, this->header.play_addr ); + } + else if ( this->cpu.r.pc > 0xFFFF ) + { + /* warning( "PC wrapped around\n" ); */ + this->cpu.r.pc &= 0xFFFF; + } + else + { + /* warning( "Emulation error (illegal/unsupported instruction)" ); */ + this->cpu.r.pc = (this->cpu.r.pc + 1) & 0xFFFF; + Cpu_set_time( &this->cpu, Cpu_time( &this->cpu ) + 6 ); + } + } + + return 0; +} + +blargg_err_t End_frame( struct Gbs_Emu* this, int end ) +{ + RETURN_ERR( Run_until( this, end ) ); + + this->next_play -= end; + if ( this->next_play < 0 ) // happens when play routine takes too long + { + #if !defined(GBS_IGNORE_STARVED_PLAY) + check( false ); + #endif + this->next_play = 0; + } + + Apu_end_frame( &this->apu, end ); + + return 0; +} + +blargg_err_t Run_clocks( struct Gbs_Emu* this, blip_time_t duration ) +{ + return End_frame( this, duration ); +} + +blargg_err_t play_( struct Gbs_Emu* this, long count, sample_t* out ) +{ + long remain = count; + while ( remain ) + { + remain -= Buffer_read_samples( &this->stereo_buf, &out [count - remain], remain ); + if ( remain ) + { + if ( this->buf_changed_count != Buffer_channels_changed_count( &this->stereo_buf ) ) + { + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); + + // Remute voices + Sound_mute_voices( this, this->mute_mask_ ); + } + int msec = Buffer_length( &this->stereo_buf ); + blip_time_t clocks_emulated = (blargg_long) msec * this->clock_rate_ / 1000; + RETURN_ERR( Run_clocks( this, clocks_emulated ) ); + assert( clocks_emulated ); + Buffer_end_frame( &this->stereo_buf, clocks_emulated ); + } + } + return 0; +} + +blargg_err_t Gbs_set_sample_rate( struct Gbs_Emu* this, long rate ) +{ + require( !this->sample_rate_ ); // sample rate can't be changed once set + Buffer_init( &this->stereo_buf ); + RETURN_ERR( Buffer_set_sample_rate( &this->stereo_buf, rate, 1000 / 20 ) ); + + // Set bass frequency + Buffer_bass_freq( &this->stereo_buf, 300 ); + + this->sample_rate_ = rate; + return 0; +} + + +// Sound + +void Sound_mute_voice( struct Gbs_Emu* this, int index, bool mute ) +{ + require( (unsigned) index < (unsigned) this->voice_count_ ); + int bit = 1 << index; + int mask = this->mute_mask_ | bit; + if ( !mute ) + mask ^= bit; + Sound_mute_voices( this, mask ); +} + +void Sound_mute_voices( struct Gbs_Emu* this, int mask ) +{ + require( this->sample_rate_ ); // sample rate must be set first + this->mute_mask_ = mask; + + int i; + for ( i = this->voice_count_; i--; ) + { + if ( mask & (1 << i) ) + { + Apu_set_output( &this->apu, i, 0, 0, 0 ); + } + else + { + struct channel_t ch = Buffer_channel( &this->stereo_buf ); + assert( (ch.center && ch.left && ch.right) || + (!ch.center && !ch.left && !ch.right) ); // all or nothing + Apu_set_output( &this->apu, i, ch.center, ch.left, ch.right ); + } + } +} + +void Sound_set_tempo( struct Gbs_Emu* this, double t ) +{ + require( this->sample_rate_ ); // sample rate must be set first + double const min = 0.02; + double const max = 4.00; + if ( t < min ) t = min; + if ( t > max ) t = max; + this->tempo_ = t; + + this->tempo = (int) (tempo_unit / t + 0.5 ); + Apu_set_tempo( &this->apu, t ); + Update_timer( this ); +} + +void fill_buf( struct Gbs_Emu* this ); +blargg_err_t Gbs_start_track( struct Gbs_Emu* this, int track ) +{ + clear_track_vars( this ); + + // Remap track if playlist available + if ( this->m3u.size > 0 ) { + struct entry_t* e = &this->m3u.entries[track]; + track = e->track; + } + + this->current_track_ = track; + + Buffer_clear( &this->stereo_buf ); + + // Reset APU to state expected by most rips + static byte const sound_data [] ICONST_ATTR = { + 0x80, 0xBF, 0x00, 0x00, 0xB8, // square 1 DAC disabled + 0x00, 0x3F, 0x00, 0x00, 0xB8, // square 2 DAC disabled + 0x7F, 0xFF, 0x9F, 0x00, 0xB8, // wave DAC disabled + 0x00, 0xFF, 0x00, 0x00, 0xB8, // noise DAC disabled + 0x77, 0xFF, 0x80, // max volume, all chans in center, power on + }; + + enum sound_t mode = this->sound_hardware; + if ( mode == sound_gbs ) + mode = (this->header.timer_mode & 0x80) ? sound_cgb : sound_dmg; + + Apu_reset( &this->apu, (enum gb_mode_t) mode, false ); + Apu_write_register( &this->apu, 0, 0xFF26, 0x80 ); // power on + int i; + for ( i = 0; i < (int) sizeof sound_data; i++ ) + Apu_write_register( &this->apu, 0, i + io_addr, sound_data [i] ); + Apu_end_frame( &this->apu, 1 ); // necessary to get click out of the way */ + + memset( this->ram, 0, 0x4000 ); + memset( this->ram + 0x4000, 0xFF, 0x1F80 ); + memset( this->ram + 0x5F80, 0, sizeof this->ram - 0x5F80 ); + this->ram [hi_page] = 0; // joypad reads back as 0 + this->ram [idle_addr - ram_addr] = 0xED; // illegal instruction + this->ram [hi_page + 6] = this->header.timer_modulo; + this->ram [hi_page + 7] = this->header.timer_mode; + + Cpu_reset( &this->cpu, this->rom.unmapped ); + Cpu_map_code( &this->cpu, ram_addr, 0x10000 - ram_addr, this->ram ); + Cpu_map_code( &this->cpu, 0, this->rom.bank_size, Rom_at_addr( &this->rom, 0 ) ); + Set_bank( this, this->rom.size > this->rom.bank_size ); + + Update_timer( this ); + this->next_play = this->play_period; + this->cpu.r.rp.fa = track; + this->cpu.r.sp = get_le16( this->header.stack_ptr ); + this->cpu_time = 0; + Jsr_then_stop( this, this->header.init_addr ); + + this->emu_track_ended_ = false; + this->track_ended = false; + + if ( !this->ignore_silence ) + { + // play until non-silence or end of track + long end; + for ( end = this->max_initial_silence * stereo * this->sample_rate_; this->emu_time < end; ) + { + fill_buf( this ); + if ( this->buf_remain | (int) this->emu_track_ended_ ) + break; + } + + this->emu_time = this->buf_remain; + this->out_time = 0; + this->silence_time = 0; + this->silence_count = 0; + } + /* return track_ended() ? warning() : 0; */ + return 0; +} + + +// Track + +blargg_long msec_to_samples( blargg_long msec, long sample_rate ) +{ + blargg_long sec = msec / 1000; + msec -= sec * 1000; + return (sec * sample_rate + msec * sample_rate / 1000) * stereo; +} + +long Track_tell( struct Gbs_Emu* this ) +{ + blargg_long rate = this->sample_rate_ * stereo; + blargg_long sec = this->out_time / rate; + return sec * 1000 + (this->out_time - sec * rate) * 1000 / rate; +} + +blargg_err_t Track_seek( struct Gbs_Emu* this, long msec ) +{ + blargg_long time = msec_to_samples( msec, this->sample_rate_ ); + if ( time < this->out_time ) + RETURN_ERR( Gbs_start_track( this, this->current_track_ ) ); + return Track_skip( this, time - this->out_time ); +} + +blargg_err_t skip_( struct Gbs_Emu* this, long count ) +{ + // for long skip, mute sound + const long threshold = 30000; + if ( count > threshold ) + { + int saved_mute = this->mute_mask_; + Sound_mute_voices( this, ~0 ); + + while ( count > threshold / 2 && !this->emu_track_ended_ ) + { + RETURN_ERR( play_( this, buf_size, this->buf ) ); + count -= buf_size; + } + + Sound_mute_voices( this, saved_mute ); + } + + while ( count && !this->emu_track_ended_ ) + { + long n = buf_size; + if ( n > count ) + n = count; + count -= n; + RETURN_ERR( play_( this, n, this->buf ) ); + } + return 0; +} + +blargg_err_t Track_skip( struct Gbs_Emu* this, long count ) +{ + require( this->current_track_ >= 0 ); // start_track() must have been called already + this->out_time += count; + + // remove from silence and buf first + { + long n = min( count, this->silence_count ); + this->silence_count -= n; + count -= n; + + n = min( count, this->buf_remain ); + this->buf_remain -= n; + count -= n; + } + + if ( count && !this->emu_track_ended_ ) + { + this->emu_time += count; + // End track if error + if ( skip_( this, count ) ) + this->emu_track_ended_ = true; + } + + if ( !(this->silence_count | this->buf_remain) ) // caught up to emulator, so update track ended + this->track_ended |= this->emu_track_ended_; + + return 0; +} + +// Fading + +void Track_set_fade( struct Gbs_Emu* this, long start_msec, long length_msec ) +{ + this->fade_step = this->sample_rate_ * length_msec / (fade_block_size * fade_shift * 1000 / stereo); + this->fade_start = msec_to_samples( start_msec, this->sample_rate_ ); +} + +// unit / pow( 2.0, (double) x / step ) +static int int_log( blargg_long x, int step, int unit ) +{ + int shift = x / step; + int fraction = (x - shift * step) * unit / step; + return ((unit - fraction) + (fraction >> 1)) >> shift; +} + +void handle_fade( struct Gbs_Emu* this, long out_count, sample_t* out ) +{ + int i; + for ( i = 0; i < out_count; i += fade_block_size ) + { + int const shift = 14; + int const unit = 1 << shift; + int gain = int_log( (this->out_time + i - this->fade_start) / fade_block_size, + this->fade_step, unit ); + if ( gain < (unit >> fade_shift) ) + this->track_ended = this->emu_track_ended_ = true; + + sample_t* io = &out [i]; + int count; + for ( count = min( fade_block_size, out_count - i ); count; --count ) + { + *io = (sample_t) ((*io * gain) >> shift); + ++io; + } + } +} + +// Silence detection + +void emu_play( struct Gbs_Emu* this, long count, sample_t* out ) +{ + check( current_track_ >= 0 ); + this->emu_time += count; + if ( this->current_track_ >= 0 && !this->emu_track_ended_ ) { + // End track if error + if ( play_( this, count, out ) ) this->emu_track_ended_ = true; + } + else + memset( out, 0, count * sizeof *out ); +} + +// number of consecutive silent samples at end +static long count_silence( sample_t* begin, long size ) +{ + sample_t first = *begin; + *begin = silence_threshold; // sentinel + sample_t* p = begin + size; + while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { } + *begin = first; + return size - (p - begin); +} + +// fill internal buffer and check it for silence +void fill_buf( struct Gbs_Emu* this ) +{ + assert( !this->buf_remain ); + if ( !this->emu_track_ended_ ) + { + emu_play( this, buf_size, this->buf ); + long silence = count_silence( this->buf, buf_size ); + if ( silence < buf_size ) + { + this->silence_time = this->emu_time - silence; + this->buf_remain = buf_size; + return; + } + } + this->silence_count += buf_size; +} + +blargg_err_t Gbs_play( struct Gbs_Emu* this, long out_count, sample_t* out ) +{ + if ( this->track_ended ) + { + memset( out, 0, out_count * sizeof *out ); + } + else + { + require( this->current_track_ >= 0 ); + require( out_count % stereo == 0 ); + + assert( this->emu_time >= this->out_time ); + + long pos = 0; + if ( this->silence_count ) + { + // during a run of silence, run emulator at >=2x speed so it gets ahead + long ahead_time = this->silence_lookahead * (this->out_time + out_count - this->silence_time) + this->silence_time; + while ( this->emu_time < ahead_time && !(this->buf_remain | this->emu_track_ended_) ) + fill_buf( this ); + + // fill with silence + pos = min( this->silence_count, out_count ); + memset( out, 0, pos * sizeof *out ); + this->silence_count -= pos; + + if ( this->emu_time - this->silence_time > silence_max * stereo * this->sample_rate_ ) + { + this->track_ended = this->emu_track_ended_ = true; + this->silence_count = 0; + this->buf_remain = 0; + } + } + + if ( this->buf_remain ) + { + // empty silence buf + long n = min( this->buf_remain, out_count - pos ); + memcpy( &out [pos], this->buf + (buf_size - this->buf_remain), n * sizeof *out ); + this->buf_remain -= n; + pos += n; + } + + // generate remaining samples normally + long remain = out_count - pos; + if ( remain ) + { + emu_play( this, remain, out + pos ); + this->track_ended |= this->emu_track_ended_; + + if ( !this->ignore_silence || this->out_time > this->fade_start ) + { + // check end for a new run of silence + long silence = count_silence( out + pos, remain ); + if ( silence < remain ) + this->silence_time = this->emu_time - silence; + + if ( this->emu_time - this->silence_time >= buf_size ) + fill_buf( this ); // cause silence detection on next play() + } + } + + if ( this->out_time > this->fade_start ) + handle_fade( this, out_count, out ); + } + this->out_time += out_count; + return 0; +} diff --git a/apps/codecs/libgme/gbs_emu.h b/apps/codecs/libgme/gbs_emu.h new file mode 100644 index 0000000000..c107264f9f --- /dev/null +++ b/apps/codecs/libgme/gbs_emu.h @@ -0,0 +1,204 @@ +// Nintendo Game Boy GBS music file emulator + +// Game_Music_Emu 0.5.2 +#ifndef GBS_EMU_H +#define GBS_EMU_H + +#include "rom_data.h" +#include "multi_buffer.h" +#include "gb_apu.h" +#include "gb_cpu.h" +#include "m3u_playlist.h" + +/* typedef uint8_t byte; */ +typedef short sample_t; + +enum { joypad_addr = 0xFF00 }; +enum { ram_addr = 0xA000 }; +enum { hi_page = 0xFF00 - ram_addr }; +enum { io_base = 0xFF00 }; +enum { buf_size = 2048 }; + +// Selects which sound hardware to use. AGB hardware is cleaner than the +// others. Doesn't take effect until next start_track(). +enum sound_t { + sound_dmg = mode_dmg, // Game Boy monochrome + sound_cgb = mode_cgb, // Game Boy Color + sound_agb = mode_agb, // Game Boy Advance + sound_gbs // Use DMG/CGB based on GBS (default) +}; + +// GBS file header +enum { header_size = 112 }; +struct header_t +{ + char tag [3]; + byte vers; + byte track_count; + byte first_track; + byte load_addr [2]; + byte init_addr [2]; + byte play_addr [2]; + byte stack_ptr [2]; + byte timer_modulo; + byte timer_mode; + char game [32]; + char author [32]; + char copyright [32]; +}; + +struct Gbs_Emu { + enum sound_t sound_hardware; + + int tempo; + + // timer + blip_time_t cpu_time; + blip_time_t end_time; + blip_time_t play_period; + blip_time_t next_play; + + // Sound + long clock_rate_; + long sample_rate_; + unsigned buf_changed_count; + int voice_count_; + double gain_; + double tempo_; + + // track-specific + byte track_count; + volatile bool track_ended; + int current_track_; + blargg_long out_time; // number of samples played since start of track + blargg_long emu_time; // number of samples emulator has generated since start of track + bool emu_track_ended_; // emulator has reached end of track + + // fading + blargg_long fade_start; + int fade_step; + + // silence detection + // Disable automatic end-of-track detection and skipping of silence at beginning + bool ignore_silence; + + int max_initial_silence; + int mute_mask_; + int silence_lookahead; // speed to run emulator when looking ahead for silence + long silence_time; // number of samples where most recent silence began + long silence_count; // number of samples of silence to play before using buf + long buf_remain; // number of samples left in silence buffer + + // Larger items at the end + // Header for currently loaded file + struct header_t header; + + // M3u Playlist + struct M3u_Playlist m3u; + + struct Gb_Apu apu; + struct Gb_Cpu cpu; + struct Stereo_Buffer stereo_buf; + + sample_t buf [buf_size]; + + // rom & ram + struct Rom_Data rom; + byte ram [0x4000 + 0x2000 + cpu_padding]; +}; + + +// Basic functionality +// Initializes Gbs_Emu structure +void Gbs_init( struct Gbs_Emu* this ); + +// Stops (clear) Gbs_Emu structure +void Gbs_stop( struct Gbs_Emu* this ); + +// Loads a file from memory +blargg_err_t Gbs_load( struct Gbs_Emu* this, void* data, long size ); + +// Set output sample rate. Must be called only once before loading file. +blargg_err_t Gbs_set_sample_rate( struct Gbs_Emu* this, long sample_rate ); + +// Start a track, where 0 is the first track. Also clears warning string. +blargg_err_t Gbs_start_track( struct Gbs_Emu* this, int ); + +// Generate 'count' samples info 'buf'. Output is in stereo. Any emulation +// errors set warning string, and major errors also end track. +blargg_err_t Gbs_play( struct Gbs_Emu* this, long count, sample_t* buf ) ICODE_ATTR; + +// Track status/control +// Number of milliseconds (1000 msec = 1 second) played since beginning of track +long Track_tell( struct Gbs_Emu* this ); + +// Seek to new time in track. Seeking backwards or far forward can take a while. +blargg_err_t Track_seek( struct Gbs_Emu* this, long msec ); + +// Skip n samples +blargg_err_t Track_skip( struct Gbs_Emu* this, long n ); + +// Set start time and length of track fade out. Once fade ends track_ended() returns +// true. Fade time can be changed while track is playing. +void Track_set_fade( struct Gbs_Emu* this, long start_msec, long length_msec ); + +// Get track length in milliseconds +static inline long Track_get_length( struct Gbs_Emu* this, int n ) +{ + long length = 120 * 1000; /* 2 minutes */ + if ( (this->m3u.size > 0) && (n < this->m3u.size) ) { + struct entry_t* entry = &this->m3u.entries [n]; + length = entry->length; + } + + return length; +} + + +// Sound customization +// Adjust song tempo, where 1.0 = normal, 0.5 = half speed, 2.0 = double speed. +// Track length as returned by track_info() assumes a tempo of 1.0. +void Sound_set_tempo( struct Gbs_Emu* this, double ); + +// Mute/unmute voice i, where voice 0 is first voice +void Sound_mute_voice( struct Gbs_Emu* this, int index, bool mute ); + +// Set muting state of all voices at once using a bit mask, where -1 mutes them all, +// 0 unmutes them all, 0x01 mutes just the first voice, etc. +void Sound_mute_voices( struct Gbs_Emu* this, int mask ); + +// Change overall output amplitude, where 1.0 results in minimal clamping. +// Must be called before set_sample_rate(). +static inline void Sound_set_gain( struct Gbs_Emu* this, double g ) +{ + assert( !this->sample_rate_ ); // you must set gain before setting sample rate + this->gain_ = g; +} + + +// Emulation (You shouldn't touch these) + +blargg_err_t Run_clocks( struct Gbs_Emu* this, blip_time_t duration ) ICODE_ATTR; +void Set_bank( struct Gbs_Emu* this, int ) ICODE_ATTR; +void Update_timer( struct Gbs_Emu* this ) ICODE_ATTR; + +// Runs CPU until time becomes >= 0 +void Run_cpu( struct Gbs_Emu* this ) ICODE_ATTR; + +// Reads/writes memory and I/O +int Read_mem( struct Gbs_Emu* this, addr_t addr ) ICODE_ATTR; +void Write_mem( struct Gbs_Emu* this, addr_t addr, int data ) ICODE_ATTR; + +// Current time +static inline blip_time_t Time( struct Gbs_Emu* this ) +{ + return Cpu_time( &this->cpu ) + this->end_time; +} + +void Jsr_then_stop( struct Gbs_Emu* this, byte const [] ) ICODE_ATTR; +void Write_io_inline( struct Gbs_Emu* this, int offset, int data, int base ) ICODE_ATTR; +void Write_io_( struct Gbs_Emu* this, int offset, int data ) ICODE_ATTR; +int Read_io( struct Gbs_Emu* this, int offset ) ICODE_ATTR; +void Write_io( struct Gbs_Emu* this, int offset, int data ) ICODE_ATTR; + +#endif diff --git a/apps/codecs/libgme/gme.h b/apps/codecs/libgme/gme.h new file mode 100644 index 0000000000..d6803704ce --- /dev/null +++ b/apps/codecs/libgme/gme.h @@ -0,0 +1,18 @@ +/* Game music emulator library C interface (also usable from C++) */ + +/* Game_Music_Emu 0.5.2 */ +#ifndef GME_H +#define GME_H + +#ifdef __cplusplus + extern "C" { +#endif + +/* Error string returned by library functions, or NULL if no error (success) */ +typedef const char* gme_err_t; + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/apps/codecs/libgme/gme_types.h b/apps/codecs/libgme/gme_types.h new file mode 100644 index 0000000000..06226f4aa1 --- /dev/null +++ b/apps/codecs/libgme/gme_types.h @@ -0,0 +1,21 @@ +#ifndef GME_TYPES_H +#define GME_TYPES_H + +/* + * This is a default gme_types.h for use when *not* using + * CMake. If CMake is in use gme_types.h.in will be + * processed instead. + */ +#define USE_GME_AY +#define USE_GME_GBS +#define USE_GME_GYM +#define USE_GME_HES +#define USE_GME_KSS +#define USE_GME_NSF +#define USE_GME_NSFE +#define USE_GME_SAP +#define USE_GME_SPC +/* VGM and VGZ are a package deal */ +#define USE_GME_VGM + +#endif /* GME_TYPES_H */ diff --git a/apps/codecs/libgme/hes_apu.c b/apps/codecs/libgme/hes_apu.c new file mode 100644 index 0000000000..a3af054548 --- /dev/null +++ b/apps/codecs/libgme/hes_apu.c @@ -0,0 +1,315 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "hes_apu.h" +#include + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +enum { center_waves = 1 }; // reduces asymmetry and clamping when starting notes + +static void Apu_balance_changed( struct Hes_Apu* this, struct Hes_Osc* osc ) ICODE_ATTR; +static void Apu_balance_changed( struct Hes_Apu* this, struct Hes_Osc* osc ) +{ + static short const log_table [32] ICONST_ATTR = { // ~1.5 db per step + #define ENTRY( factor ) (short) (factor * amp_range / 31.0 + 0.5) + ENTRY( 0.000000 ),ENTRY( 0.005524 ),ENTRY( 0.006570 ),ENTRY( 0.007813 ), + ENTRY( 0.009291 ),ENTRY( 0.011049 ),ENTRY( 0.013139 ),ENTRY( 0.015625 ), + ENTRY( 0.018581 ),ENTRY( 0.022097 ),ENTRY( 0.026278 ),ENTRY( 0.031250 ), + ENTRY( 0.037163 ),ENTRY( 0.044194 ),ENTRY( 0.052556 ),ENTRY( 0.062500 ), + ENTRY( 0.074325 ),ENTRY( 0.088388 ),ENTRY( 0.105112 ),ENTRY( 0.125000 ), + ENTRY( 0.148651 ),ENTRY( 0.176777 ),ENTRY( 0.210224 ),ENTRY( 0.250000 ), + ENTRY( 0.297302 ),ENTRY( 0.353553 ),ENTRY( 0.420448 ),ENTRY( 0.500000 ), + ENTRY( 0.594604 ),ENTRY( 0.707107 ),ENTRY( 0.840896 ),ENTRY( 1.000000 ), + #undef ENTRY + }; + + int vol = (osc->control & 0x1F) - 0x1E * 2; + + int left = vol + (osc->balance >> 3 & 0x1E) + (this->balance >> 3 & 0x1E); + if ( left < 0 ) left = 0; + + int right = vol + (osc->balance << 1 & 0x1E) + (this->balance << 1 & 0x1E); + if ( right < 0 ) right = 0; + + left = log_table [left ]; + right = log_table [right]; + + // optimizing for the common case of being centered also allows easy + // panning using Effects_Buffer + osc->outputs [0] = osc->chans [0]; // center + osc->outputs [1] = 0; + if ( left != right ) + { + osc->outputs [0] = osc->chans [1]; // left + osc->outputs [1] = osc->chans [2]; // right + } + + if ( center_waves ) + { + osc->last_amp [0] += (left - osc->volume [0]) * 16; + osc->last_amp [1] += (right - osc->volume [1]) * 16; + } + + osc->volume [0] = left; + osc->volume [1] = right; +} + +void Apu_init( struct Hes_Apu* this ) +{ + struct Hes_Osc* osc = &this->oscs [osc_count]; + do + { + osc--; + osc->outputs [0] = 0; + osc->outputs [1] = 0; + osc->chans [0] = 0; + osc->chans [1] = 0; + osc->chans [2] = 0; + } + while ( osc != this->oscs ); + + Apu_reset( this ); +} + +void Apu_reset( struct Hes_Apu* this ) +{ + this->latch = 0; + this->balance = 0xFF; + + struct Hes_Osc* osc = &this->oscs [osc_count]; + do + { + osc--; + memset( osc, 0, offsetof (struct Hes_Osc,outputs) ); + osc->noise_lfsr = 1; + osc->control = 0x40; + osc->balance = 0xFF; + } + while ( osc != this->oscs ); +} + +void Apu_osc_output( struct Hes_Apu* this, int index, struct Blip_Buffer* center, struct Blip_Buffer* left, struct Blip_Buffer* right ) +{ + require( (unsigned) index < osc_count ); + this->oscs [index].chans [0] = center; + this->oscs [index].chans [1] = left; + this->oscs [index].chans [2] = right; + + struct Hes_Osc* osc = &this->oscs [osc_count]; + do + { + osc--; + Apu_balance_changed( this, osc ); + } + while ( osc != this->oscs ); +} + +void Osc_run_until( struct Hes_Osc* this, struct Blip_Synth* synth_, blip_time_t end_time ) +{ + struct Blip_Buffer* const osc_outputs_0 = this->outputs [0]; // cache often-used values + if ( osc_outputs_0 && this->control & 0x80 ) + { + int dac = this->dac; + + int const volume_0 = this->volume [0]; + { + int delta = dac * volume_0 - this->last_amp [0]; + if ( delta ) + Synth_offset( synth_, this->last_time, delta, osc_outputs_0 ); + Blip_set_modified( osc_outputs_0 ); + } + + struct Blip_Buffer* const osc_outputs_1 = this->outputs [1]; + int const volume_1 = this->volume [1]; + if ( osc_outputs_1 ) + { + int delta = dac * volume_1 - this->last_amp [1]; + if ( delta ) + Synth_offset( synth_, this->last_time, delta, osc_outputs_1 ); + Blip_set_modified( osc_outputs_1 ); + } + + blip_time_t time = this->last_time + this->delay; + if ( time < end_time ) + { + if ( this->noise & 0x80 ) + { + if ( volume_0 | volume_1 ) + { + // noise + int const period = (32 - (this->noise & 0x1F)) * 64; // TODO: correct? + unsigned noise_lfsr = this->noise_lfsr; + do + { + int new_dac = 0x1F & -(noise_lfsr >> 1 & 1); + // Implemented using "Galios configuration" + // TODO: find correct LFSR algorithm + noise_lfsr = (noise_lfsr >> 1) ^ (0xE008 & -(noise_lfsr & 1)); + //noise_lfsr = (noise_lfsr >> 1) ^ (0x6000 & -(noise_lfsr & 1)); + int delta = new_dac - dac; + if ( delta ) + { + dac = new_dac; + Synth_offset( synth_, time, delta * volume_0, osc_outputs_0 ); + if ( osc_outputs_1 ) + Synth_offset( synth_, time, delta * volume_1, osc_outputs_1 ); + } + time += period; + } + while ( time < end_time ); + + this->noise_lfsr = noise_lfsr; + assert( noise_lfsr ); + } + } + else if ( !(this->control & 0x40) ) + { + // wave + int phase = (this->phase + 1) & 0x1F; // pre-advance for optimal inner loop + int period = this->period * 2; + if ( period >= 14 && (volume_0 | volume_1) ) + { + do + { + int new_dac = this->wave [phase]; + phase = (phase + 1) & 0x1F; + int delta = new_dac - dac; + if ( delta ) + { + dac = new_dac; + Synth_offset( synth_, time, delta * volume_0, osc_outputs_0 ); + if ( osc_outputs_1 ) + Synth_offset( synth_, time, delta * volume_1, osc_outputs_1 ); + } + time += period; + } + while ( time < end_time ); + } + else + { + if ( !period ) + { + // TODO: Gekisha Boy assumes that period = 0 silences wave + //period = 0x1000 * 2; + period = 1; + //if ( !(volume_0 | volume_1) ) + // dprintf( "Used period 0\n" ); + } + + // maintain phase when silent + blargg_long count = (end_time - time + period - 1) / period; + phase += count; // phase will be masked below + time += count * period; + } + this->phase = (phase - 1) & 0x1F; // undo pre-advance + } + } + time -= end_time; + if ( time < 0 ) + time = 0; + this->delay = time; + + this->dac = dac; + this->last_amp [0] = dac * volume_0; + this->last_amp [1] = dac * volume_1; + } + this->last_time = end_time; +} + +void Apu_write_data( struct Hes_Apu* this, blip_time_t time, int addr, int data ) +{ + if ( addr == 0x800 ) + { + this->latch = data & 7; + } + else if ( addr == 0x801 ) + { + if ( this->balance != data ) + { + this->balance = data; + + struct Hes_Osc* osc = &this->oscs [osc_count]; + do + { + osc--; + Osc_run_until( osc, &this->synth, time ); + Apu_balance_changed( this, this->oscs ); + } + while ( osc != this->oscs ); + } + } + else if ( this->latch < osc_count ) + { + struct Hes_Osc* osc = &this->oscs [this->latch]; + Osc_run_until( osc, &this->synth, time ); + switch ( addr ) + { + case 0x802: + osc->period = (osc->period & 0xF00) | data; + break; + + case 0x803: + osc->period = (osc->period & 0x0FF) | ((data & 0x0F) << 8); + break; + + case 0x804: + if ( osc->control & 0x40 & ~data ) + osc->phase = 0; + osc->control = data; + Apu_balance_changed( this, osc ); + break; + + case 0x805: + osc->balance = data; + Apu_balance_changed( this, osc ); + break; + + case 0x806: + data &= 0x1F; + if ( !(osc->control & 0x40) ) + { + osc->wave [osc->phase] = data; + osc->phase = (osc->phase + 1) & 0x1F; + } + else if ( osc->control & 0x80 ) + { + osc->dac = data; + } + break; + + case 0x807: + if ( osc >= &this->oscs [4] ) + osc->noise = data; + break; + case 0x809: + if ( !(data & 0x80) && (data & 0x03) != 0 ) { + dprintf( "HES LFO not supported\n" ); + } + } + } +} + +void Apu_end_frame( struct Hes_Apu* this, blip_time_t end_time ) +{ + struct Hes_Osc* osc = &this->oscs [osc_count]; + do + { + osc--; + if ( end_time > osc->last_time ) + Osc_run_until( osc, &this->synth, end_time ); + assert( osc->last_time >= end_time ); + osc->last_time -= end_time; + } + while ( osc != this->oscs ); +} diff --git a/apps/codecs/libgme/hes_apu.h b/apps/codecs/libgme/hes_apu.h new file mode 100644 index 0000000000..8a49a5afc7 --- /dev/null +++ b/apps/codecs/libgme/hes_apu.h @@ -0,0 +1,55 @@ +// Turbo Grafx 16 (PC Engine) PSG sound chip emulator + +// Game_Music_Emu 0.5.2 +#ifndef HES_APU_H +#define HES_APU_H + +#include "blargg_common.h" +#include "blip_buffer.h" + +enum { amp_range = 0x8000 }; +enum { osc_count = 6 }; +enum { start_addr = 0x0800 }; +enum { end_addr = 0x0809 }; + +struct Hes_Osc +{ + unsigned char wave [32]; + short volume [2]; + int last_amp [2]; + int delay; + int period; + unsigned char noise; + unsigned char phase; + unsigned char balance; + unsigned char dac; + blip_time_t last_time; + + struct Blip_Buffer* outputs [2]; + struct Blip_Buffer* chans [3]; + unsigned noise_lfsr; + unsigned char control; +}; + +void Osc_run_until( struct Hes_Osc* this, struct Blip_Synth* synth, blip_time_t ) ICODE_ATTR; + +struct Hes_Apu { + struct Hes_Osc oscs [osc_count]; + + int latch; + int balance; + struct Blip_Synth synth; +}; + +// Init HES apu sound chip +void Apu_init( struct Hes_Apu* this ); + +// Reset HES apu couns chip +void Apu_reset( struct Hes_Apu* this ); + +void Apu_osc_output( struct Hes_Apu* this, int index, struct Blip_Buffer* center, struct Blip_Buffer* left, struct Blip_Buffer* right ) ICODE_ATTR; +void Apu_write_data( struct Hes_Apu* this, blip_time_t, int addr, int data ) ICODE_ATTR; +void Apu_end_frame( struct Hes_Apu* this, blip_time_t ) ICODE_ATTR; + +static inline void Apu_volume( struct Hes_Apu* this, double v ) { Synth_volume( &this->synth, 1.8 / osc_count / amp_range * v ); } +#endif diff --git a/apps/codecs/libgme/hes_apu_adpcm.c b/apps/codecs/libgme/hes_apu_adpcm.c new file mode 100644 index 0000000000..b2f78ff71f --- /dev/null +++ b/apps/codecs/libgme/hes_apu_adpcm.c @@ -0,0 +1,297 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "hes_apu_adpcm.h" + +/* Copyright (C) 2006-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + + +void Adpcm_init( struct Hes_Apu_Adpcm* this ) +{ + this->output = NULL; + memset( &this->state, 0, sizeof( this->state ) ); + Adpcm_reset( this ); +} + +void Adpcm_reset( struct Hes_Apu_Adpcm* this ) +{ + this->last_time = 0; + this->next_timer = 0; + this->last_amp = 0; + + memset( &this->state.pcmbuf, 0, sizeof(this->state.pcmbuf) ); + memset( &this->state.port, 0, sizeof(this->state.port) ); + + this->state.ad_sample = 0; + this->state.ad_ref_index = 0; + + this->state.addr = 0; + this->state.freq = 0; + this->state.writeptr = 0; + this->state.readptr = 0; + this->state.playflag = 0; + this->state.repeatflag = 0; + this->state.length = 0; + this->state.volume = 0xFF; + this->state.fadetimer = 0; + this->state.fadecount = 0; +} + +static short stepsize[49] = { + 16, 17, 19, 21, 23, 25, 28, + 31, 34, 37, 41, 45, 50, 55, + 60, 66, 73, 80, 88, 97, 107, + 118, 130, 143, 157, 173, 190, 209, + 230, 253, 279, 307, 337, 371, 408, + 449, 494, 544, 598, 658, 724, 796, + 876, 963,1060,1166,1282,1411,1552 +}; + +static int Adpcm_decode( struct Hes_Apu_Adpcm* this,int code ) ICODE_ATTR; +static int Adpcm_decode( struct Hes_Apu_Adpcm* this,int code ) +{ + struct State* state = &this->state; + int step = stepsize[state->ad_ref_index]; + int delta; + int c = code & 7; +#if 1 + delta = 0; + if ( c & 4 ) delta += step; + step >>= 1; + if ( c & 2 ) delta += step; + step >>= 1; + if ( c & 1 ) delta += step; + step >>= 1; + delta += step; +#else + delta = ( ( c + c + 1 ) * step ) / 8; // maybe faster, but introduces rounding +#endif + if ( c != code ) + { + state->ad_sample -= delta; + if ( state->ad_sample < -2048 ) + state->ad_sample = -2048; + } + else + { + state->ad_sample += delta; + if ( state->ad_sample > 2047 ) + state->ad_sample = 2047; + } + + static int const steps [8] ICONST_ATTR = { + -1, -1, -1, -1, 2, 4, 6, 8 + }; + state->ad_ref_index += steps [c]; + if ( state->ad_ref_index < 0 ) + state->ad_ref_index = 0; + else if ( state->ad_ref_index > 48 ) + state->ad_ref_index = 48; + + return state->ad_sample; +} + +static void Adpcm_run_until( struct Hes_Apu_Adpcm* this, blip_time_t end_time ) ICODE_ATTR; +static void Adpcm_run_until( struct Hes_Apu_Adpcm* this, blip_time_t end_time ) +{ + struct State* state = &this->state; + int volume = state->volume; + int fadetimer = state->fadetimer; + int fadecount = state->fadecount; + int last_time = this->last_time; + double next_timer = this->next_timer; + int last_amp = this->last_amp; + + struct Blip_Buffer* output = this->output; // cache often-used values + + while ( state->playflag && last_time < end_time ) + { + while ( last_time >= next_timer ) + { + if ( fadetimer ) + { + if ( fadecount > 0 ) + { + fadecount--; + volume = 0xFF * fadecount / fadetimer; + } + else if ( fadecount < 0 ) + { + fadecount++; + volume = 0xFF - ( 0xFF * fadecount / fadetimer ); + } + } + next_timer += 7159.091; + } + int amp; + if ( state->ad_low_nibble ) + { + amp = Adpcm_decode( this, state->pcmbuf[ state->playptr ] & 0x0F ); + state->ad_low_nibble = false; + state->playptr++; + state->playedsamplecount++; + if ( state->playedsamplecount == state->playlength ) + { + state->playflag = 0; + } + } + else + { + amp = Adpcm_decode( this, state->pcmbuf[ state->playptr ] >> 4 ); + state->ad_low_nibble = true; + } + amp = amp * volume / 0xFF; + int delta = amp - last_amp; + if ( output && delta ) + { + last_amp = amp; + Synth_offset_inline( &this->synth, last_time, delta, output ); + } + last_time += state->freq; + } + + if ( !state->playflag ) + { + while ( next_timer <= end_time ) next_timer += 7159.091; + last_time = end_time; + } + + this->last_time = last_time; + this->next_timer = next_timer; + this->last_amp = last_amp; + state->volume = volume; + state->fadetimer = fadetimer; + state->fadecount = fadecount; +} + +void Adpcm_write_data( struct Hes_Apu_Adpcm* this, blip_time_t time, int addr, int data ) +{ + if ( time > this->last_time ) Adpcm_run_until( this, time ); + struct State* state = &this->state; + + data &= 0xFF; + state->port[ addr & 15 ] = data; + switch ( addr & 15 ) + { + case 8: + state->addr &= 0xFF00; + state->addr |= data; + break; + case 9: + state->addr &= 0xFF; + state->addr |= data << 8; + break; + case 10: + state->pcmbuf[ state->writeptr++ ] = data; + state->playlength ++; + break; + case 11: + dprintf("ADPCM DMA 0x%02X", data); + break; + case 13: + if ( data & 0x80 ) + { + state->addr = 0; + state->freq = 0; + state->writeptr = 0; + state->readptr = 0; + state->playflag = 0; + state->repeatflag = 0; + state->length = 0; + state->volume = 0xFF; + } + if ( ( data & 3 ) == 3 ) + { + state->writeptr = state->addr; + } + if ( data & 8 ) + { + state->readptr = state->addr ? state->addr - 1 : state->addr; + } + if ( data & 0x10 ) + { + state->length = state->addr; + } + state->repeatflag = data & 0x20; + state->playflag = data & 0x40; + if ( state->playflag ) + { + state->playptr = state->readptr; + state->playlength = state->length + 1; + state->playedsamplecount = 0; + state->ad_sample = 0; + state->ad_low_nibble = false; + } + break; + case 14: + state->freq = 7159091 / ( 32000 / ( 16 - ( data & 15 ) ) ); + break; + case 15: + switch ( data & 15 ) + { + case 0: + case 8: + case 12: + state->fadetimer = -100; + state->fadecount = state->fadetimer; + break; + case 10: + state->fadetimer = 5000; + state->fadecount = state->fadetimer; + break; + case 14: + state->fadetimer = 1500; + state->fadecount = state->fadetimer; + break; + } + break; + } +} + +int Adpcm_read_data( struct Hes_Apu_Adpcm* this, blip_time_t time, int addr ) +{ + if ( time > this->last_time ) Adpcm_run_until( this, time ); + + struct State* state = &this->state; + switch ( addr & 15 ) + { + case 10: + return state->pcmbuf [state->readptr++]; + case 11: + return state->port [11] & ~1; + case 12: + if (!state->playflag) + { + state->port [12] |= 1; + state->port [12] &= ~8; + } + else + { + state->port [12] &= ~1; + state->port [12] |= 8; + } + return state->port [12]; + case 13: + return state->port [13]; + } + + return 0xFF; +} + +void Adpcm_end_frame( struct Hes_Apu_Adpcm* this, blip_time_t end_time ) +{ + Adpcm_run_until( this, end_time ); + this->last_time -= end_time; + this->next_timer -= (double)end_time; + check( last_time >= 0 ); + if ( this->output ) + Blip_set_modified( this->output ); +} diff --git a/apps/codecs/libgme/hes_apu_adpcm.h b/apps/codecs/libgme/hes_apu_adpcm.h new file mode 100644 index 0000000000..5478f2b360 --- /dev/null +++ b/apps/codecs/libgme/hes_apu_adpcm.h @@ -0,0 +1,89 @@ +// Turbo Grafx 16 (PC Engine) ADPCM sound chip emulator + +// Game_Music_Emu 0.6-pre +#ifndef HES_APU_ADPCM_H +#define HES_APU_ADPCM_H + +#include "blargg_source.h" +#include "blargg_common.h" +#include "blip_buffer.h" + +enum { adpcm_amp_range = 2048 }; +enum { adpcm_osc_count = 1 }; // 0 <= chan < osc_count + +// Registers are at io_addr to io_addr+io_size-1 +enum { io_addr = 0x1800 }; +enum { io_size = 0x400 }; + +struct State +{ + byte pcmbuf [0x10000]; + byte port [0x10]; + int ad_sample; + int ad_ref_index; + bool ad_low_nibble; + int freq; + unsigned short addr; + unsigned short writeptr; + unsigned short readptr; + unsigned short playptr; + byte playflag; + byte repeatflag; + int length; + int playlength; + int playedsamplecount; + int volume; + int fadetimer; + int fadecount; +}; + +struct Hes_Apu_Adpcm { + struct State state; + struct Blip_Synth synth; + + struct Blip_Buffer* output; + blip_time_t last_time; + double next_timer; + int last_amp; +}; + +// Init HES adpcm sound chip +void Adpcm_init( struct Hes_Apu_Adpcm* this ); + +// Rest HES adpcm sound chip +void Adpcm_reset( struct Hes_Apu_Adpcm* this ); + +// Sets buffer(s) to generate sound into, or 0 to mute. If only center is not 0, +// output is mono. +static inline void Adpcm_set_output( struct Hes_Apu_Adpcm* this, int chan, struct Blip_Buffer* center, struct Blip_Buffer* left, struct Blip_Buffer* right ) +{ + // Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL) + require( !center || (center && !left && !right) || (center && left && right) ); + require( (unsigned) chan < adpcm_osc_count ); // fails if you pass invalid osc index + +#if defined(ROCKBOX) + (void) chan; +#endif + + if ( !center || !left || !right ) + { + left = center; + right = center; + } + + this->output = center; +} + +// Emulates to time t, then writes data to addr +void Adpcm_write_data( struct Hes_Apu_Adpcm* this, blip_time_t t, int addr, int data ) ICODE_ATTR; + +// Emulates to time t, then reads from addr +int Adpcm_read_data( struct Hes_Apu_Adpcm* this, blip_time_t t, int addr ) ICODE_ATTR; + +// Emulates to time t, then subtracts t from the current time. +// OK if previous write call had time slightly after t. +void Adpcm_end_frame( struct Hes_Apu_Adpcm* this,blip_time_t t ) ICODE_ATTR; + +// Sets overall volume, where 1.0 is normal +static inline void Adpcm_volume( struct Hes_Apu_Adpcm* this, double v ) { Synth_volume( &this->synth, 0.6 / adpcm_osc_count / adpcm_amp_range * v ); } +#endif diff --git a/apps/codecs/libgme/hes_cpu.c b/apps/codecs/libgme/hes_cpu.c new file mode 100644 index 0000000000..08dfb5e993 --- /dev/null +++ b/apps/codecs/libgme/hes_cpu.c @@ -0,0 +1,1321 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "hes_cpu.h" + +#include "blargg_endian.h" + +//#include "hes_cpu_log.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +// TODO: support T flag, including clearing it at appropriate times? + +// all zero-page should really use whatever is at page 1, but that would +// reduce efficiency quite a bit +int const ram_addr = 0x2000; + +#define FLUSH_TIME() (void) (s.time = s_time) +#define CACHE_TIME() (void) (s_time = s.time) + +#include "hes_cpu_io.h" + +#include "blargg_source.h" + +#ifdef BLARGG_NONPORTABLE + #define PAGE_OFFSET( addr ) (addr) +#else + #define PAGE_OFFSET( addr ) ((addr) & (page_size - 1)) +#endif + +// status flags +int const st_n = 0x80; +int const st_v = 0x40; +int const st_t = 0x20; +int const st_b = 0x10; +int const st_d = 0x08; +int const st_i = 0x04; +int const st_z = 0x02; +int const st_c = 0x01; + +void Cpu_init( struct Hes_Cpu* this ) +{ + this->state = &this->state_; +} + +void Cpu_reset( struct Hes_Cpu* this ) +{ + check( this->state == &state_ ); + this->state = &this->state_; + + this->state_.time = 0; + this->state_.base = 0; + this->irq_time = future_hes_time; + this->end_time = future_hes_time; + + this->r.status = st_i; + this->r.sp = 0; + this->r.pc = 0; + this->r.a = 0; + this->r.x = 0; + this->r.y = 0; + + blargg_verify_byte_order(); +} + +void Cpu_set_mmr( struct Hes_Emu* this, int reg, int bank ) +{ + assert( (unsigned) reg <= page_count ); // allow page past end to be set + assert( (unsigned) bank < 0x100 ); + this->cpu.mmr [reg] = bank; + uint8_t const* code = CPU_SET_MMR( this, reg, bank ); + this->cpu.state->code_map [reg] = code - PAGE_OFFSET( reg << page_shift ); +} + +#define TIME (s_time + s.base) + +#define READ( addr ) CPU_READ( this, (addr), TIME ) +#define WRITE( addr, data ) {CPU_WRITE( this, (addr), (data), TIME );} +#define READ_LOW( addr ) (cpu->ram [(int) (addr)]) +#define WRITE_LOW( addr, data ) (void) (READ_LOW( addr ) = (data)) +#define READ_PROG( addr ) (s.code_map [(addr) >> page_shift] [PAGE_OFFSET( addr )]) + +#define SET_SP( v ) (sp = ((v) + 1) | 0x100) +#define GET_SP() ((sp - 1) & 0xFF) +#define PUSH( v ) ((sp = (sp - 1) | 0x100), WRITE_LOW( sp, v )) + +// even on x86, using short and unsigned char was slower +typedef int fint16; +typedef unsigned fuint16; +typedef unsigned fuint8; +typedef blargg_long fint32; + +bool Cpu_run( struct Hes_Emu* this, hes_time_t end_time ) +{ + bool illegal_encountered = false; + + // Set cpu end time + struct Hes_Cpu* cpu = &this->cpu; + cpu->state->time += Cpu_update_end_time( cpu, cpu->r.status, (cpu->end_time = end_time), cpu->irq_time ); + + struct state_t s = cpu->state_; + cpu->state = &s; + + // even on x86, using s.time in place of s_time was slower + fint16 s_time = s.time; + + struct registers_t* r = &cpu->r; + + // registers + fuint16 pc = r->pc; + fuint8 a = r->a; + fuint8 x = r->x; + fuint8 y = r->y; + fuint16 sp; + SET_SP( r->sp ); + + #define IS_NEG (nz & 0x8080) + + #define CALC_STATUS( out ) do {\ + out = status & (st_v | st_d | st_i);\ + out |= ((nz >> 8) | nz) & st_n;\ + out |= c >> 8 & st_c;\ + if ( !(nz & 0xFF) ) out |= st_z;\ + } while ( 0 ) + + #define SET_STATUS( in ) do {\ + status = in & (st_v | st_d | st_i);\ + nz = in << 8;\ + c = nz;\ + nz |= ~in & st_z;\ + } while ( 0 ) + + fuint8 status; + fuint16 c; // carry set if (c & 0x100) != 0 + fuint16 nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x8080) != 0 + { + fuint8 temp = r->status; + SET_STATUS( temp ); + } + + goto loop; +branch_not_taken: + s_time -= 2; +loop: + + #ifndef NDEBUG + { + hes_time_t correct = end_time_; + if ( !(status & st_i) && correct > irq_time_ ) + correct = irq_time_; + check( s.base == correct ); + /* + static long count; + if ( count == 1844 ) Debugger(); + if ( s.base != correct ) dprintf( "%ld\n", count ); + count++; + */ + } + #endif + + check( (unsigned) GET_SP() < 0x100 ); + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + + uint8_t const* instr = s.code_map [pc >> page_shift]; + fuint8 opcode; + + // TODO: eliminate this special case + #ifdef BLARGG_NONPORTABLE + opcode = instr [pc]; + pc++; + instr += pc; + #else + instr += PAGE_OFFSET( pc ); + opcode = *instr++; + pc++; + #endif + + // TODO: each reference lists slightly different timing values, ugh + static uint8_t const clock_table [256] ICONST_ATTR = + {// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 1,7,3, 4,6,4,6,7,3,2,2,2,7,5,7,6,// 0 + 4,7,7, 4,6,4,6,7,2,5,2,2,7,5,7,6,// 1 + 7,7,3, 4,4,4,6,7,4,2,2,2,5,5,7,6,// 2 + 4,7,7, 2,4,4,6,7,2,5,2,2,5,5,7,6,// 3 + 7,7,3, 4,8,4,6,7,3,2,2,2,4,5,7,6,// 4 + 4,7,7, 5,2,4,6,7,2,5,3,2,2,5,7,6,// 5 + 7,7,2, 2,4,4,6,7,4,2,2,2,7,5,7,6,// 6 + 4,7,7,17,4,4,6,7,2,5,4,2,7,5,7,6,// 7 + 4,7,2, 7,4,4,4,7,2,2,2,2,5,5,5,6,// 8 + 4,7,7, 8,4,4,4,7,2,5,2,2,5,5,5,6,// 9 + 2,7,2, 7,4,4,4,7,2,2,2,2,5,5,5,6,// A + 4,7,7, 8,4,4,4,7,2,5,2,2,5,5,5,6,// B + 2,7,2,17,4,4,6,7,2,2,2,2,5,5,7,6,// C + 4,7,7,17,2,4,6,7,2,5,3,2,2,5,7,6,// D + 2,7,2,17,4,4,6,7,2,2,2,2,5,5,7,6,// E + 4,7,7,17,2,4,6,7,2,5,4,2,2,5,7,6 // F + }; // 0x00 was 8 + + fuint16 data; + data = clock_table [opcode]; + if ( (s_time += data) >= 0 ) + goto possibly_out_of_time; +almost_out_of_time: + + data = *instr; + + #ifdef HES_CPU_LOG_H + log_cpu( "new", pc - 1, opcode, instr [0], instr [1], instr [2], + instr [3], instr [4], instr [5] ); + //log_opcode( opcode ); + #endif + + switch ( opcode ) + { +possibly_out_of_time: + if ( s_time < (int) data ) + goto almost_out_of_time; + s_time -= data; + goto out_of_time; + +// Macros + +#define GET_MSB() (instr [1]) +#define ADD_PAGE( out ) (pc++, out = data + 0x100 * GET_MSB()); +#define GET_ADDR() GET_LE16( instr ) + +// TODO: is the penalty really always added? the original 6502 was much better +//#define PAGE_CROSS_PENALTY( lsb ) (void) (s_time += (lsb) >> 8) +#define PAGE_CROSS_PENALTY( lsb ) + +// Branch + +// TODO: more efficient way to handle negative branch that wraps PC around +#define BRANCH( cond )\ +{\ + fint16 offset = (int8_t) data;\ + pc++;\ + if ( !(cond) ) goto branch_not_taken;\ + pc = (uint16_t) (pc + offset);\ + goto loop;\ +} + + case 0xF0: // BEQ + BRANCH( !((uint8_t) nz) ); + + case 0xD0: // BNE + BRANCH( (uint8_t) nz ); + + case 0x10: // BPL + BRANCH( !IS_NEG ); + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + + case 0x30: // BMI + BRANCH( IS_NEG ) + + case 0x50: // BVC + BRANCH( !(status & st_v) ) + + case 0x70: // BVS + BRANCH( status & st_v ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x80: // BRA + branch_taken: + BRANCH( true ); + + case 0xFF: + if ( pc == idle_addr + 1 ) + goto idle_done; + case 0x0F: // BBRn + case 0x1F: + case 0x2F: + case 0x3F: + case 0x4F: + case 0x5F: + case 0x6F: + case 0x7F: + case 0x8F: // BBSn + case 0x9F: + case 0xAF: + case 0xBF: + case 0xCF: + case 0xDF: + case 0xEF: { + fuint16 t = 0x101 * READ_LOW( data ); + t ^= 0xFF; + pc++; + data = GET_MSB(); + BRANCH( t & (1 << (opcode >> 4)) ) + } + + case 0x4C: // JMP abs + pc = GET_ADDR(); + goto loop; + + case 0x7C: // JMP (ind+X) + data += x; + case 0x6C:{// JMP (ind) + data += 0x100 * GET_MSB(); + pc = GET_LE16( &READ_PROG( data ) ); + goto loop; + } + +// Subroutine + + case 0x44: // BSR + WRITE_LOW( 0x100 | (sp - 1), pc >> 8 ); + sp = (sp - 2) | 0x100; + WRITE_LOW( sp, pc ); + goto branch_taken; + + case 0x20: { // JSR + fuint16 temp = pc + 1; + pc = GET_ADDR(); + WRITE_LOW( 0x100 | (sp - 1), temp >> 8 ); + sp = (sp - 2) | 0x100; + WRITE_LOW( sp, temp ); + goto loop; + } + + case 0x60: // RTS + pc = 0x100 * READ_LOW( 0x100 | (sp - 0xFF) ); + pc += 1 + READ_LOW( sp ); + sp = (sp - 0xFE) | 0x100; + goto loop; + + case 0x00: // BRK + goto handle_brk; + +// Common + + case 0xBD:{// LDA abs,X + PAGE_CROSS_PENALTY( data + x ); + fuint16 addr = GET_ADDR() + x; + pc += 2; + CPU_READ_FAST( this, addr, TIME, nz ); + a = nz; + goto loop; + } + + case 0x9D:{// STA abs,X + fuint16 addr = GET_ADDR() + x; + pc += 2; + CPU_WRITE_FAST( this, addr, a, TIME ); + goto loop; + } + + case 0x95: // STA zp,x + data = (uint8_t) (data + x); + case 0x85: // STA zp + pc++; + WRITE_LOW( data, a ); + goto loop; + + case 0xAE:{// LDX abs + fuint16 addr = GET_ADDR(); + pc += 2; + CPU_READ_FAST( this, addr, TIME, nz ); + x = nz; + goto loop; + } + + case 0xA5: // LDA zp + a = nz = READ_LOW( data ); + pc++; + goto loop; + +// Load/store + + { + fuint16 addr; + case 0x91: // STA (ind),Y + addr = 0x100 * READ_LOW( (uint8_t) (data + 1) ); + addr += READ_LOW( data ) + y; + pc++; + goto sta_ptr; + + case 0x81: // STA (ind,X) + data = (uint8_t) (data + x); + case 0x92: // STA (ind) + addr = 0x100 * READ_LOW( (uint8_t) (data + 1) ); + addr += READ_LOW( data ); + pc++; + goto sta_ptr; + + case 0x99: // STA abs,Y + data += y; + case 0x8D: // STA abs + addr = data + 0x100 * GET_MSB(); + pc += 2; + sta_ptr: + CPU_WRITE_FAST( this, addr, a, TIME ); + goto loop; + } + + { + fuint16 addr; + case 0xA1: // LDA (ind,X) + data = (uint8_t) (data + x); + case 0xB2: // LDA (ind) + addr = 0x100 * READ_LOW( (uint8_t) (data + 1) ); + addr += READ_LOW( data ); + pc++; + goto a_nz_read_addr; + + case 0xB1:// LDA (ind),Y + addr = READ_LOW( data ) + y; + PAGE_CROSS_PENALTY( addr ); + addr += 0x100 * READ_LOW( (uint8_t) (data + 1) ); + pc++; + goto a_nz_read_addr; + + case 0xB9: // LDA abs,Y + data += y; + PAGE_CROSS_PENALTY( data ); + case 0xAD: // LDA abs + addr = data + 0x100 * GET_MSB(); + pc += 2; + a_nz_read_addr: + CPU_READ_FAST( this, addr, TIME, nz ); + a = nz; + goto loop; + } + + case 0xBE:{// LDX abs,y + PAGE_CROSS_PENALTY( data + y ); + fuint16 addr = GET_ADDR() + y; + pc += 2; + FLUSH_TIME(); + x = nz = READ( addr ); + CACHE_TIME(); + goto loop; + } + + case 0xB5: // LDA zp,x + a = nz = READ_LOW( (uint8_t) (data + x) ); + pc++; + goto loop; + + case 0xA9: // LDA #imm + pc++; + a = data; + nz = data; + goto loop; + +// Bit operations + + case 0x3C: // BIT abs,x + data += x; + case 0x2C:{// BIT abs + fuint16 addr; + ADD_PAGE( addr ); + FLUSH_TIME(); + nz = READ( addr ); + CACHE_TIME(); + goto bit_common; + } + case 0x34: // BIT zp,x + data = (uint8_t) (data + x); + case 0x24: // BIT zp + data = READ_LOW( data ); + case 0x89: // BIT imm + nz = data; + bit_common: + pc++; + status &= ~st_v; + status |= nz & st_v; + if ( nz & a ) + goto loop; // Z should be clear, and nz must be non-zero if nz & a is + nz <<= 8; // set Z flag without affecting N flag + goto loop; + + { + fuint16 addr; + + case 0xB3: // TST abs,x + addr = GET_MSB() + x; + goto tst_abs; + + case 0x93: // TST abs + addr = GET_MSB(); + tst_abs: + addr += 0x100 * instr [2]; + pc++; + FLUSH_TIME(); + nz = READ( addr ); + CACHE_TIME(); + goto tst_common; + } + + case 0xA3: // TST zp,x + nz = READ_LOW( (uint8_t) (GET_MSB() + x) ); + goto tst_common; + + case 0x83: // TST zp + nz = READ_LOW( GET_MSB() ); + tst_common: + pc += 2; + status &= ~st_v; + status |= nz & st_v; + if ( nz & data ) + goto loop; // Z should be clear, and nz must be non-zero if nz & data is + nz <<= 8; // set Z flag without affecting N flag + goto loop; + + { + fuint16 addr; + case 0x0C: // TSB abs + case 0x1C: // TRB abs + addr = GET_ADDR(); + pc++; + goto txb_addr; + + // TODO: everyone lists different behaviors for the status flags, ugh + case 0x04: // TSB zp + case 0x14: // TRB zp + addr = data + ram_addr; + txb_addr: + FLUSH_TIME(); + nz = a | READ( addr ); + if ( opcode & 0x10 ) + nz ^= a; // bits from a will already be set, so this clears them + status &= ~st_v; + status |= nz & st_v; + pc++; + WRITE( addr, nz ); + CACHE_TIME(); + goto loop; + } + + case 0x07: // RMBn + case 0x17: + case 0x27: + case 0x37: + case 0x47: + case 0x57: + case 0x67: + case 0x77: + pc++; + READ_LOW( data ) &= ~(1 << (opcode >> 4)); + goto loop; + + case 0x87: // SMBn + case 0x97: + case 0xA7: + case 0xB7: + case 0xC7: + case 0xD7: + case 0xE7: + case 0xF7: + pc++; + READ_LOW( data ) |= 1 << ((opcode >> 4) - 8); + goto loop; + +// Load/store + + case 0x9E: // STZ abs,x + data += x; + case 0x9C: // STZ abs + ADD_PAGE( data ); + pc++; + FLUSH_TIME(); + WRITE( data, 0 ); + CACHE_TIME(); + goto loop; + + case 0x74: // STZ zp,x + data = (uint8_t) (data + x); + case 0x64: // STZ zp + pc++; + WRITE_LOW( data, 0 ); + goto loop; + + case 0x94: // STY zp,x + data = (uint8_t) (data + x); + case 0x84: // STY zp + pc++; + WRITE_LOW( data, y ); + goto loop; + + case 0x96: // STX zp,y + data = (uint8_t) (data + y); + case 0x86: // STX zp + pc++; + WRITE_LOW( data, x ); + goto loop; + + case 0xB6: // LDX zp,y + data = (uint8_t) (data + y); + case 0xA6: // LDX zp + data = READ_LOW( data ); + case 0xA2: // LDX #imm + pc++; + x = data; + nz = data; + goto loop; + + case 0xB4: // LDY zp,x + data = (uint8_t) (data + x); + case 0xA4: // LDY zp + data = READ_LOW( data ); + case 0xA0: // LDY #imm + pc++; + y = data; + nz = data; + goto loop; + + case 0xBC: // LDY abs,X + data += x; + PAGE_CROSS_PENALTY( data ); + case 0xAC:{// LDY abs + fuint16 addr = data + 0x100 * GET_MSB(); + pc += 2; + FLUSH_TIME(); + y = nz = READ( addr ); + CACHE_TIME(); + goto loop; + } + + { + fuint8 temp; + case 0x8C: // STY abs + temp = y; + goto store_abs; + + case 0x8E: // STX abs + temp = x; + store_abs: + { + fuint16 addr = GET_ADDR(); + pc += 2; + FLUSH_TIME(); + WRITE( addr, temp ); + CACHE_TIME(); + goto loop; + } + } + +// Compare + + case 0xEC:{// CPX abs + fuint16 addr = GET_ADDR(); + pc++; + FLUSH_TIME(); + data = READ( addr ); + CACHE_TIME(); + goto cpx_data; + } + + case 0xE4: // CPX zp + data = READ_LOW( data ); + case 0xE0: // CPX #imm + cpx_data: + nz = x - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0xCC:{// CPY abs + fuint16 addr = GET_ADDR(); + pc++; + FLUSH_TIME(); + data = READ( addr ); + CACHE_TIME(); + goto cpy_data; + } + + case 0xC4: // CPY zp + data = READ_LOW( data ); + case 0xC0: // CPY #imm + cpy_data: + nz = y - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + +// Logical + +#define ARITH_ADDR_MODES( op )\ + case op - 0x04: /* (ind,x) */\ + data = (uint8_t) (data + x);\ + case op + 0x0D: /* (ind) */\ + data = 0x100 * READ_LOW( (uint8_t) (data + 1) ) + READ_LOW( data );\ + goto ptr##op;\ + case op + 0x0C:{/* (ind),y */\ + fuint16 temp = READ_LOW( data ) + y;\ + PAGE_CROSS_PENALTY( temp );\ + data = temp + 0x100 * READ_LOW( (uint8_t) (data + 1) );\ + goto ptr##op;\ + }\ + case op + 0x10: /* zp,X */\ + data = (uint8_t) (data + x);\ + case op + 0x00: /* zp */\ + data = READ_LOW( data );\ + goto imm##op;\ + case op + 0x14: /* abs,Y */\ + data += y;\ + goto ind##op;\ + case op + 0x18: /* abs,X */\ + data += x;\ + ind##op:\ + PAGE_CROSS_PENALTY( data );\ + case op + 0x08: /* abs */\ + ADD_PAGE( data );\ + ptr##op:\ + FLUSH_TIME();\ + data = READ( data );\ + CACHE_TIME();\ + case op + 0x04: /* imm */\ + imm##op: + + ARITH_ADDR_MODES( 0xC5 ) // CMP + nz = a - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + ARITH_ADDR_MODES( 0x25 ) // AND + nz = (a &= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x45 ) // EOR + nz = (a ^= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x05 ) // ORA + nz = (a |= data); + pc++; + goto loop; + +// Add/subtract + + ARITH_ADDR_MODES( 0xE5 ) // SBC + data ^= 0xFF; + goto adc_imm; + + ARITH_ADDR_MODES( 0x65 ) // ADC + adc_imm: { + if ( status & st_d ) { + dprintf( "Decimal mode not supported\n" ); + } + fint16 carry = c >> 8 & 1; + fint16 ov = (a ^ 0x80) + carry + (int8_t) data; // sign-extend + status &= ~st_v; + status |= ov >> 2 & 0x40; + c = nz = a + data + carry; + pc++; + a = (uint8_t) nz; + goto loop; + } + +// Shift/rotate + + case 0x4A: // LSR A + c = 0; + case 0x6A: // ROR A + nz = c >> 1 & 0x80; + c = a << 8; + nz |= a >> 1; + a = nz; + goto loop; + + case 0x0A: // ASL A + nz = a << 1; + c = nz; + a = (uint8_t) nz; + goto loop; + + case 0x2A: { // ROL A + nz = a << 1; + fint16 temp = c >> 8 & 1; + c = nz; + nz |= temp; + a = (uint8_t) nz; + goto loop; + } + + case 0x5E: // LSR abs,X + data += x; + case 0x4E: // LSR abs + c = 0; + case 0x6E: // ROR abs + ror_abs: { + ADD_PAGE( data ); + FLUSH_TIME(); + int temp = READ( data ); + nz = (c >> 1 & 0x80) | (temp >> 1); + c = temp << 8; + goto rotate_common; + } + + case 0x3E: // ROL abs,X + data += x; + goto rol_abs; + + case 0x1E: // ASL abs,X + data += x; + case 0x0E: // ASL abs + c = 0; + case 0x2E: // ROL abs + rol_abs: + ADD_PAGE( data ); + nz = c >> 8 & 1; + FLUSH_TIME(); + nz |= (c = READ( data ) << 1); + rotate_common: + pc++; + WRITE( data, (uint8_t) nz ); + CACHE_TIME(); + goto loop; + + case 0x7E: // ROR abs,X + data += x; + goto ror_abs; + + case 0x76: // ROR zp,x + data = (uint8_t) (data + x); + goto ror_zp; + + case 0x56: // LSR zp,x + data = (uint8_t) (data + x); + case 0x46: // LSR zp + c = 0; + case 0x66: // ROR zp + ror_zp: { + int temp = READ_LOW( data ); + nz = (c >> 1 & 0x80) | (temp >> 1); + c = temp << 8; + goto write_nz_zp; + } + + case 0x36: // ROL zp,x + data = (uint8_t) (data + x); + goto rol_zp; + + case 0x16: // ASL zp,x + data = (uint8_t) (data + x); + case 0x06: // ASL zp + c = 0; + case 0x26: // ROL zp + rol_zp: + nz = c >> 8 & 1; + nz |= (c = READ_LOW( data ) << 1); + goto write_nz_zp; + +// Increment/decrement + +#define INC_DEC_AXY( reg, n ) reg = (uint8_t) (nz = reg + n); goto loop; + + case 0x1A: // INA + INC_DEC_AXY( a, +1 ) + + case 0xE8: // INX + INC_DEC_AXY( x, +1 ) + + case 0xC8: // INY + INC_DEC_AXY( y, +1 ) + + case 0x3A: // DEA + INC_DEC_AXY( a, -1 ) + + case 0xCA: // DEX + INC_DEC_AXY( x, -1 ) + + case 0x88: // DEY + INC_DEC_AXY( y, -1 ) + + case 0xF6: // INC zp,x + data = (uint8_t) (data + x); + case 0xE6: // INC zp + nz = 1; + goto add_nz_zp; + + case 0xD6: // DEC zp,x + data = (uint8_t) (data + x); + case 0xC6: // DEC zp + nz = (unsigned) -1; + add_nz_zp: + nz += READ_LOW( data ); + write_nz_zp: + pc++; + WRITE_LOW( data, nz ); + goto loop; + + case 0xFE: // INC abs,x + data = x + GET_ADDR(); + goto inc_ptr; + + case 0xEE: // INC abs + data = GET_ADDR(); + inc_ptr: + nz = 1; + goto inc_common; + + case 0xDE: // DEC abs,x + data = x + GET_ADDR(); + goto dec_ptr; + + case 0xCE: // DEC abs + data = GET_ADDR(); + dec_ptr: + nz = (unsigned) -1; + inc_common: + FLUSH_TIME(); + nz += READ( data ); + pc += 2; + WRITE( data, (uint8_t) nz ); + CACHE_TIME(); + goto loop; + +// Transfer + + case 0xA8: // TAY + y = a; + nz = a; + goto loop; + + case 0x98: // TYA + a = y; + nz = y; + goto loop; + + case 0xAA: // TAX + x = a; + nz = a; + goto loop; + + case 0x8A: // TXA + a = x; + nz = x; + goto loop; + + case 0x9A: // TXS + SET_SP( x ); // verified (no flag change) + goto loop; + + case 0xBA: // TSX + x = nz = GET_SP(); + goto loop; + + #define SWAP_REGS( r1, r2 ) {\ + fuint8 t = r1;\ + r1 = r2;\ + r2 = t;\ + goto loop;\ + } + + case 0x02: // SXY + SWAP_REGS( x, y ); + + case 0x22: // SAX + SWAP_REGS( a, x ); + + case 0x42: // SAY + SWAP_REGS( a, y ); + + case 0x62: // CLA + a = 0; + goto loop; + + case 0x82: // CLX + x = 0; + goto loop; + + case 0xC2: // CLY + y = 0; + goto loop; + +// Stack + + case 0x48: // PHA + PUSH( a ); + goto loop; + + case 0xDA: // PHX + PUSH( x ); + goto loop; + + case 0x5A: // PHY + PUSH( y ); + goto loop; + + case 0x40:{// RTI + fuint8 temp = READ_LOW( sp ); + pc = READ_LOW( 0x100 | (sp - 0xFF) ); + pc |= READ_LOW( 0x100 | (sp - 0xFE) ) * 0x100; + sp = (sp - 0xFD) | 0x100; + data = status; + SET_STATUS( temp ); + r->status = status; // update externally-visible I flag + if ( (data ^ status) & st_i ) + { + hes_time_t new_time = cpu->end_time; + if ( !(status & st_i) && new_time > cpu->irq_time ) + new_time = cpu->irq_time; + blargg_long delta = s.base - new_time; + s.base = new_time; + s_time += delta; + } + goto loop; + } + + #define POP() READ_LOW( sp ); sp = (sp - 0xFF) | 0x100 + + case 0x68: // PLA + a = nz = POP(); + goto loop; + + case 0xFA: // PLX + x = nz = POP(); + goto loop; + + case 0x7A: // PLY + y = nz = POP(); + goto loop; + + case 0x28:{// PLP + fuint8 temp = POP(); + fuint8 changed = status ^ temp; + SET_STATUS( temp ); + if ( !(changed & st_i) ) + goto loop; // I flag didn't change + if ( status & st_i ) + goto handle_sei; + goto handle_cli; + } + #undef POP + + case 0x08: { // PHP + fuint8 temp; + CALC_STATUS( temp ); + PUSH( temp | st_b ); + goto loop; + } + +// Flags + + case 0x38: // SEC + c = (unsigned) ~0; + goto loop; + + case 0x18: // CLC + c = 0; + goto loop; + + case 0xB8: // CLV + status &= ~st_v; + goto loop; + + case 0xD8: // CLD + status &= ~st_d; + goto loop; + + case 0xF8: // SED + status |= st_d; + goto loop; + + case 0x58: // CLI + if ( !(status & st_i) ) + goto loop; + status &= ~st_i; + handle_cli: { + r->status = status; // update externally-visible I flag + blargg_long delta = s.base - cpu->irq_time; + if ( delta <= 0 ) + { + if ( TIME < cpu->irq_time ) + goto loop; + goto delayed_cli; + } + s.base = cpu->irq_time; + s_time += delta; + if ( s_time < 0 ) + goto loop; + + if ( delta >= s_time + 1 ) + { + // delayed irq until after next instruction + s.base += s_time + 1; + s_time = -1; + cpu->irq_time = s.base; // TODO: remove, as only to satisfy debug check in loop + goto loop; + } + delayed_cli: + dprintf( "Delayed CLI not supported\n" ); // TODO: implement + goto loop; + } + + case 0x78: // SEI + if ( status & st_i ) + goto loop; + status |= st_i; + handle_sei: { + r->status = status; // update externally-visible I flag + blargg_long delta = s.base - cpu->end_time; + s.base = cpu->end_time; + s_time += delta; + if ( s_time < 0 ) + goto loop; + dprintf( "Delayed SEI not supported\n" ); // TODO: implement + goto loop; + } + +// Special + + case 0x53:{// TAM + fuint8 const bits = data; // avoid using data across function call + pc++; + int i; + for ( i = 0; i < 8; i++ ) + if ( bits & (1 << i) ) + /* this->cpu.set_mmr( i, a ); */ + Cpu_set_mmr( this, i, a ); + goto loop; + } + + case 0x43:{// TMA + pc++; + byte const* in = cpu->mmr; + do + { + if ( data & 1 ) + a = *in; + in++; + } + while ( (data >>= 1) != 0 ); + goto loop; + } + + case 0x03: // ST0 + case 0x13: // ST1 + case 0x23:{// ST2 + fuint16 addr = opcode >> 4; + if ( addr ) + addr++; + pc++; + FLUSH_TIME(); + CPU_WRITE_VDP( this, addr, data, TIME ); + CACHE_TIME(); + goto loop; + } + + case 0xEA: // NOP + goto loop; + + case 0x54: // CSL + dprintf( "CSL not supported\n" ); + illegal_encountered = true; + goto loop; + + case 0xD4: // CSH + goto loop; + + case 0xF4: { // SET + //fuint16 operand = GET_MSB(); + dprintf( "SET not handled\n" ); + //switch ( data ) + //{ + //} + illegal_encountered = true; + goto loop; + } + +// Block transfer + + { + fuint16 in_alt; + fint16 in_inc; + fuint16 out_alt; + fint16 out_inc; + + case 0xE3: // TIA + in_alt = 0; + goto bxfer_alt; + + case 0xF3: // TAI + in_alt = 1; + bxfer_alt: + in_inc = in_alt ^ 1; + out_alt = in_inc; + out_inc = in_alt; + goto bxfer; + + case 0xD3: // TIN + in_inc = 1; + out_inc = 0; + goto bxfer_no_alt; + + case 0xC3: // TDD + in_inc = -1; + out_inc = -1; + goto bxfer_no_alt; + + case 0x73: // TII + in_inc = 1; + out_inc = 1; + bxfer_no_alt: + in_alt = 0; + out_alt = 0; + bxfer: { + fuint16 in = GET_LE16( instr + 0 ); + fuint16 out = GET_LE16( instr + 2 ); + int count = GET_LE16( instr + 4 ); + if ( !count ) + count = 0x10000; + pc += 6; + WRITE_LOW( 0x100 | (sp - 1), y ); + WRITE_LOW( 0x100 | (sp - 2), a ); + WRITE_LOW( 0x100 | (sp - 3), x ); + FLUSH_TIME(); + do + { + // TODO: reads from $0800-$1400 in I/O page return 0 and don't access I/O + fuint8 t = READ( in ); + in += in_inc; + in &= 0xFFFF; + s.time += 6; + if ( in_alt ) + in_inc = -in_inc; + WRITE( out, t ); + out += out_inc; + out &= 0xFFFF; + if ( out_alt ) + out_inc = -out_inc; + } + while ( --count ); + CACHE_TIME(); + goto loop; + } + } + +// Illegal + + default: + assert( (unsigned) opcode <= 0xFF ); + dprintf( "Illegal opcode $%02X at $%04X\n", (int) opcode, (int) pc - 1 ); + illegal_encountered = true; + goto loop; + } + assert( false ); + + int result_; +handle_brk: + pc++; + result_ = 6; + +interrupt: + { + s_time += 7; + + WRITE_LOW( 0x100 | (sp - 1), pc >> 8 ); + WRITE_LOW( 0x100 | (sp - 2), pc ); + pc = GET_LE16( &READ_PROG( 0xFFF0 ) + result_ ); + + sp = (sp - 3) | 0x100; + fuint8 temp; + CALC_STATUS( temp ); + if ( result_ == 6 ) + temp |= st_b; + WRITE_LOW( sp, temp ); + + status &= ~st_d; + status |= st_i; + r->status = status; // update externally-visible I flag + + blargg_long delta = s.base - cpu->end_time; + s.base = cpu->end_time; + s_time += delta; + goto loop; + } + +idle_done: + s_time = 0; +out_of_time: + pc--; + FLUSH_TIME(); + CPU_DONE( this, TIME, result_ ); + CACHE_TIME(); + if ( result_ > 0 ) + goto interrupt; + if ( s_time < 0 ) + goto loop; + + s.time = s_time; + + r->pc = pc; + r->sp = GET_SP(); + r->a = a; + r->x = x; + r->y = y; + + { + fuint8 temp; + CALC_STATUS( temp ); + r->status = temp; + } + + cpu->state_ = s; + cpu->state = &cpu->state_; + + return illegal_encountered; +} + diff --git a/apps/codecs/libgme/hes_cpu.h b/apps/codecs/libgme/hes_cpu.h new file mode 100644 index 0000000000..f3bcf7d4cf --- /dev/null +++ b/apps/codecs/libgme/hes_cpu.h @@ -0,0 +1,95 @@ +// PC Engine CPU emulator for use with HES music files + +// Game_Music_Emu 0.5.2 +#ifndef HES_CPU_H +#define HES_CPU_H + +#include "blargg_common.h" + +typedef blargg_long hes_time_t; // clock cycle count +typedef unsigned hes_addr_t; // 16-bit address + +struct Hes_Emu; + +enum { future_hes_time = LONG_MAX / 2 + 1 }; +enum { page_size = 0x2000 }; +enum { page_shift = 13 }; +enum { page_count = 8 }; + +// Attempt to execute instruction here results in CPU advancing time to +// lesser of irq_time() and end_time() (or end_time() if IRQs are +// disabled) +enum { idle_addr = 0x1FFF }; + +// Can read this many bytes past end of a page +enum { cpu_padding = 8 }; +enum { irq_inhibit = 0x04 }; + + +// Cpu state +struct state_t { + uint8_t const* code_map [page_count + 1]; + hes_time_t base; + blargg_long time; +}; + +// Cpu registers +struct registers_t { + uint16_t pc; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t status; + uint8_t sp; +}; + +struct Hes_Cpu { + struct registers_t r; + + hes_time_t irq_time; + hes_time_t end_time; + + struct state_t* state; // points to state_ or a local copy within run() + struct state_t state_; + + // page mapping registers + uint8_t mmr [page_count + 1]; + uint8_t ram [page_size]; +}; + +// Init cpu state +void Cpu_init( struct Hes_Cpu* this ); + +// Reset hes cpu +void Cpu_reset( struct Hes_Cpu* this ); + +// Set end_time and run CPU from current time. Returns true if any illegal +// instructions were encountered. +bool Cpu_run( struct Hes_Emu* this, hes_time_t end_time ) ICODE_ATTR; + +void Cpu_set_mmr( struct Hes_Emu* this, int reg, int bank ) ICODE_ATTR; + +// Time of ning of next instruction to be executed +static inline hes_time_t Cpu_time( struct Hes_Cpu* this ) +{ + return this->state->time + this->state->base; +} + +static inline uint8_t const* Cpu_get_code( struct Hes_Cpu* this, hes_addr_t addr ) +{ + return this->state->code_map [addr >> page_shift] + addr + #if !defined (BLARGG_NONPORTABLE) + % (unsigned) page_size + #endif + ; +} + +static inline int Cpu_update_end_time( struct Hes_Cpu* this, uint8_t reg_status, hes_time_t t, hes_time_t irq ) +{ + if ( irq < t && !(reg_status & irq_inhibit) ) t = irq; + int delta = this->state->base - t; + this->state->base = t; + return delta; +} + +#endif diff --git a/apps/codecs/libgme/hes_cpu_io.h b/apps/codecs/libgme/hes_cpu_io.h new file mode 100644 index 0000000000..6b49c69e22 --- /dev/null +++ b/apps/codecs/libgme/hes_cpu_io.h @@ -0,0 +1,72 @@ + +#include "hes_emu.h" + +#include "blargg_source.h" + +int Cpu_read( struct Hes_Emu* this, hes_addr_t addr ) +{ + check( addr <= 0xFFFF ); + int result = *Cpu_get_code( &this->cpu, addr ); + if ( this->cpu.mmr [addr >> page_shift] == 0xFF ) + result = Emu_cpu_read( this, addr ); + return result; +} + +void Cpu_write( struct Hes_Emu* this, hes_addr_t addr, int data ) +{ + check( addr <= 0xFFFF ); + byte* out = this->write_pages [addr >> page_shift]; + addr &= page_size - 1; + if ( out ) + out [addr] = data; + else if ( this->cpu.mmr [addr >> page_shift] == 0xFF ) + Emu_cpu_write( this, addr, data ); +} + +#define CPU_READ_FAST( emu, addr, time, out ) \ + CPU_READ_FAST_( emu, addr, time, out ) + +#define CPU_READ_FAST_( emu, addr, time, out ) \ +{\ + out = READ_PROG( addr );\ + if ( emu->cpu.mmr [addr >> page_shift] == 0xFF )\ + {\ + FLUSH_TIME();\ + out = Emu_cpu_read( emu, addr );\ + CACHE_TIME();\ + }\ +} + +#define CPU_WRITE_FAST( emu, addr, data, time ) \ + CPU_WRITE_FAST_( emu, addr, data, time ) + +#define CPU_WRITE_FAST_( emu, addr, data, time ) \ +{\ + byte* out = emu->write_pages [addr >> page_shift];\ + addr &= page_size - 1;\ + if ( out )\ + {\ + out [addr] = data;\ + }\ + else if ( emu->cpu.mmr [addr >> page_shift] == 0xFF )\ + {\ + FLUSH_TIME();\ + Emu_cpu_write( emu, addr, data );\ + CACHE_TIME();\ + }\ +} + +#define CPU_READ( emu, addr, time ) \ + Cpu_read( emu, addr ) + +#define CPU_WRITE( emu, addr, data, time ) \ + Cpu_write( emu, addr, data ) + +#define CPU_WRITE_VDP( emu, addr, data, time ) \ + Cpu_write_vdp( emu, addr, data ) + +#define CPU_SET_MMR( emu, page, bank ) \ + Emu_cpu_set_mmr( emu, page, bank ) + +#define CPU_DONE( emu, time, result_out ) \ + result_out = Cpu_done( emu ) diff --git a/apps/codecs/libgme/hes_emu.c b/apps/codecs/libgme/hes_emu.c new file mode 100644 index 0000000000..a44eded8d7 --- /dev/null +++ b/apps/codecs/libgme/hes_emu.c @@ -0,0 +1,877 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "hes_emu.h" + +#include "blargg_endian.h" +#include "blargg_source.h" + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +int const timer_mask = 0x04; +int const vdp_mask = 0x02; +int const i_flag_mask = 0x04; +int const unmapped = 0xFF; + +long const period_60hz = 262 * 455L; // scanlines * clocks per scanline + +int const stereo = 2; // number of channels for stereo +int const silence_max = 6; // seconds +int const silence_threshold = 0x10; +long const fade_block_size = 512; +int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) + +const char gme_wrong_file_type [] ICONST_ATTR = "Wrong file type for this emulator"; + +void clear_track_vars( struct Hes_Emu* this ) +{ + this->current_track_ = -1; + this->out_time = 0; + this->emu_time = 0; + this->emu_track_ended_ = true; + this->track_ended = true; + this->fade_start = LONG_MAX / 2 + 1; + this->fade_step = 1; + this->silence_time = 0; + this->silence_count = 0; + this->buf_remain = 0; +} + +void Hes_init( struct Hes_Emu* this ) +{ + this->sample_rate_ = 0; + this->mute_mask_ = 0; + this->tempo_ = 1.0; + + // defaults + this->max_initial_silence = 2; + this->ignore_silence = false; + + // Unload + this->voice_count_ = 0; + clear_track_vars( this ); + + this->timer.raw_load = 0; + this->silence_lookahead = 6; + Sound_set_gain( this, 1.11 ); + + Rom_init( &this->rom, 0x2000 ); + + Apu_init( &this->apu ); + Adpcm_init( &this->adpcm ); + Cpu_init( &this->cpu ); + + /* Set default track count */ + this->track_count = 255; +} + +static blargg_err_t check_hes_header( void const* header ) +{ + if ( memcmp( header, "HESM", 4 ) ) + return gme_wrong_file_type; + return 0; +} + +// Setup + +blargg_err_t Hes_load( struct Hes_Emu* this, void* data, long size ) +{ + // Unload + this->voice_count_ = 0; + clear_track_vars( this ); + + assert( offsetof (struct header_t,unused [4]) == header_size ); + RETURN_ERR( Rom_load( &this->rom, data, size, header_size, &this->header, unmapped ) ); + + RETURN_ERR( check_hes_header( this->header.tag ) ); + + /* if ( header_.vers != 0 ) + warning( "Unknown file version" ); + + if ( memcmp( header_.data_tag, "DATA", 4 ) ) + warning( "Data header missing" ); + + if ( memcmp( header_.unused, "\0\0\0\0", 4 ) ) + warning( "Unknown header data" ); */ + + // File spec supports multiple blocks, but I haven't found any, and + // many files have bad sizes in the only block, so it's simpler to + // just try to load the damn data as best as possible. + + long addr = get_le32( this->header.addr ); + /* long rom_size = get_le32( this->header.size ); */ + long const rom_max = 0x100000; + if ( addr & ~(rom_max - 1) ) + { + /* warning( "Invalid address" ); */ + addr &= rom_max - 1; + } + /* if ( (unsigned long) (addr + size) > (unsigned long) rom_max ) + warning( "Invalid size" ); + + if ( rom_size != rom.file_size() ) + { + if ( size <= rom.file_size() - 4 && !memcmp( rom.begin() + size, "DATA", 4 ) ) + warning( "Multiple DATA not supported" ); + else if ( size < rom.file_size() ) + warning( "Extra file data" ); + else + warning( "Missing file data" ); + } */ + + Rom_set_addr( &this->rom, addr ); + + this->voice_count_ = osc_count + adpcm_osc_count; + + Apu_volume( &this->apu, this->gain_ ); + Adpcm_volume( &this->adpcm, this->gain_ ); + + // Setup buffer + this->clock_rate_ = 7159091; + Buffer_clock_rate( &this->stereo_buf, 7159091 ); + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); + + Sound_set_tempo( this, this->tempo_ ); + Sound_mute_voices( this, this->mute_mask_ ); + + // Reset track count + this->track_count = 255; + this->m3u.size = 0; + return 0; +} + + +// Emulation + +void recalc_timer_load( struct Hes_Emu* this ) ICODE_ATTR; +void recalc_timer_load( struct Hes_Emu* this ) +{ + this->timer.load = this->timer.raw_load * this->timer_base + 1; +} + +// Hardware + +void irq_changed( struct Hes_Emu* this ) ICODE_ATTR; +void run_until( struct Hes_Emu* this, hes_time_t present ) ICODE_ATTR; +void Cpu_write_vdp( struct Hes_Emu* this, int addr, int data ) +{ + switch ( addr ) + { + case 0: + this->vdp.latch = data & 0x1F; + break; + + case 2: + if ( this->vdp.latch == 5 ) + { + /* if ( data & 0x04 ) + warning( "Scanline interrupt unsupported" ); */ + run_until( this, Cpu_time( &this->cpu ) ); + this->vdp.control = data; + irq_changed( this ); + } + else + { + dprintf( "VDP not supported: $%02X <- $%02X\n", this->vdp.latch, data ); + } + break; + + case 3: + dprintf( "VDP MSB not supported: $%02X <- $%02X\n", this->vdp.latch, data ); + break; + } +} + +int Cpu_done( struct Hes_Emu* this ) +{ + check( time() >= end_time() || + (!(r.status & i_flag_mask) && time() >= irq_time()) ); + + if ( !(this->cpu.r.status & i_flag_mask) ) + { + hes_time_t present = Cpu_time( &this->cpu ); + + if ( this->irq.timer <= present && !(this->irq.disables & timer_mask) ) + { + this->timer.fired = true; + this->irq.timer = future_hes_time; + irq_changed( this ); // overkill, but not worth writing custom code + #if defined (GME_FRAME_HOOK_DEFINED) + { + unsigned const threshold = period_60hz / 30; + unsigned long elapsed = present - last_frame_hook; + if ( elapsed - period_60hz + threshold / 2 < threshold ) + { + last_frame_hook = present; + GME_FRAME_HOOK( this ); + } + } + #endif + return 0x0A; + } + + if ( this->irq.vdp <= present && !(this->irq.disables & vdp_mask) ) + { + // work around for bugs with music not acknowledging VDP + //run_until( present ); + //irq.vdp = future_hes_time; + //irq_changed(); + #if defined(GME_FRAME_HOOK_DEFINED) + last_frame_hook = present; + GME_FRAME_HOOK( this ); + #endif + return 0x08; + } + } + return 0; +} + +void Emu_cpu_write( struct Hes_Emu* this, hes_addr_t addr, int data ) +{ + hes_time_t time = Cpu_time( &this->cpu ); + if ( (unsigned) (addr - start_addr) <= end_addr - start_addr ) + { + GME_APU_HOOK( this, addr - apu.start_addr, data ); + // avoid going way past end when a long block xfer is writing to I/O space + hes_time_t t = min( time, this->cpu.end_time + 8 ); + Apu_write_data( &this->apu, t, addr, data ); + return; + } + + if ( (unsigned) (addr - io_addr) < io_size ) + { + hes_time_t t = min( time, this->cpu.end_time + 6 ); + Adpcm_write_data( &this->adpcm, t, addr, data ); + return; + } + + switch ( addr ) + { + case 0x0000: + case 0x0002: + case 0x0003: + Cpu_write_vdp( this, addr, data ); + return; + + case 0x0C00: { + run_until( this, time ); + this->timer.raw_load = (data & 0x7F) + 1; + recalc_timer_load( this ); + this->timer.count = this->timer.load; + break; + } + + case 0x0C01: + data &= 1; + if ( this->timer.enabled == data ) + return; + run_until( this, time ); + this->timer.enabled = data; + if ( data ) + this->timer.count = this->timer.load; + break; + + case 0x1402: + run_until( this, time ); + this->irq.disables = data; + + // flag questionable values + if ( (data & 0xF8) && (data & 0xF8) != 0xF8 ) { + dprintf( "Int mask: $%02X\n", data ); + } + break; + + case 0x1403: + run_until( this, time ); + if ( this->timer.enabled ) + this->timer.count = this->timer.load; + this->timer.fired = false; + break; + +#ifndef NDEBUG + case 0x1000: // I/O port + case 0x0402: // palette + case 0x0403: + case 0x0404: + case 0x0405: + return; + + default: + dprintf( "unmapped write $%04X <- $%02X\n", addr, data ); + return; +#endif + } + + irq_changed( this ); +} + +int Emu_cpu_read( struct Hes_Emu* this, hes_addr_t addr ) +{ + hes_time_t time = Cpu_time( &this->cpu ); + addr &= page_size - 1; + switch ( addr ) + { + case 0x0000: + if ( this->irq.vdp > time ) + return 0; + this->irq.vdp = future_hes_time; + run_until( this, time ); + irq_changed( this ); + return 0x20; + + case 0x0002: + case 0x0003: + dprintf( "VDP read not supported: %d\n", addr ); + return 0; + + case 0x0C01: + //return timer.enabled; // TODO: remove? + case 0x0C00: + run_until( this, time ); + dprintf( "Timer count read\n" ); + return (unsigned) (this->timer.count - 1) / this->timer_base; + + case 0x1402: + return this->irq.disables; + + case 0x1403: + { + int status = 0; + if ( this->irq.timer <= time ) status |= timer_mask; + if ( this->irq.vdp <= time ) status |= vdp_mask; + return status; + } + + case 0x180A: + case 0x180B: + case 0x180C: + case 0x180D: + return Adpcm_read_data( &this->adpcm, time, addr ); + + #ifndef NDEBUG + case 0x1000: // I/O port + // case 0x180C: // CD-ROM + // case 0x180D: + break; + + default: + dprintf( "unmapped read $%04X\n", addr ); + #endif + } + + return unmapped; +} + +// see hes_cpu_io.h for core read/write functions + +// Emulation + +void run_until( struct Hes_Emu* this, hes_time_t present ) +{ + while ( this->vdp.next_vbl < present ) + this->vdp.next_vbl += this->play_period; + + hes_time_t elapsed = present - this->timer.last_time; + if ( elapsed > 0 ) + { + if ( this->timer.enabled ) + { + this->timer.count -= elapsed; + if ( this->timer.count <= 0 ) + this->timer.count += this->timer.load; + } + this->timer.last_time = present; + } +} + +void irq_changed( struct Hes_Emu* this ) +{ + hes_time_t present = Cpu_time( &this->cpu ); + + if ( this->irq.timer > present ) + { + this->irq.timer = future_hes_time; + if ( this->timer.enabled && !this->timer.fired ) + this->irq.timer = present + this->timer.count; + } + + if ( this->irq.vdp > present ) + { + this->irq.vdp = future_hes_time; + if ( this->vdp.control & 0x08 ) + this->irq.vdp = this->vdp.next_vbl; + } + + hes_time_t time = future_hes_time; + if ( !(this->irq.disables & timer_mask) ) time = this->irq.timer; + if ( !(this->irq.disables & vdp_mask) ) time = min( time, this->irq.vdp ); + + // Set cpu irq time + this->cpu.state->time += Cpu_update_end_time( &this->cpu, this->cpu.r.status, + this->cpu.end_time, (this->cpu.irq_time = time) ); +} + +static void adjust_time( blargg_long* time, hes_time_t delta ) ICODE_ATTR; +static void adjust_time( blargg_long* time, hes_time_t delta ) +{ + if ( *time < future_hes_time ) + { + *time -= delta; + if ( *time < 0 ) + *time = 0; + } +} + +blargg_err_t run_clocks( struct Hes_Emu* this, blip_time_t* duration_ ) ICODE_ATTR; +blargg_err_t run_clocks( struct Hes_Emu* this, blip_time_t* duration_ ) +{ + blip_time_t duration = *duration_; // cache + + Cpu_run( this, duration ); + /* warning( "Emulation error (illegal instruction)" ); */ + + check( time() >= duration ); + //check( time() - duration < 20 ); // Txx instruction could cause going way over + + run_until( this, duration ); + + // end time frame + this->timer.last_time -= duration; + this->vdp.next_vbl -= duration; + #if defined (GME_FRAME_HOOK_DEFINED) + last_frame_hook -= *duration; + #endif + + // End cpu frame + this->cpu.state_.base -= duration; + if ( this->cpu.irq_time < future_hes_time ) this->cpu.irq_time -= duration; + if ( this->cpu.end_time < future_hes_time ) this->cpu.end_time -= duration; + + adjust_time( &this->irq.timer, duration ); + adjust_time( &this->irq.vdp, duration ); + Apu_end_frame( &this->apu, duration ); + Adpcm_end_frame( &this->adpcm, duration ); + + return 0; +} + +blargg_err_t play_( struct Hes_Emu* this, long count, sample_t* out ) ICODE_ATTR; +blargg_err_t play_( struct Hes_Emu* this, long count, sample_t* out ) +{ + long remain = count; + while ( remain ) + { + remain -= Buffer_read_samples( &this->stereo_buf, &out [count - remain], remain ); + if ( remain ) + { + if ( this->buf_changed_count != Buffer_channels_changed_count( &this->stereo_buf ) ) + { + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); + // Remute voices + Sound_mute_voices( this, this->mute_mask_ ); + } + + int msec = Buffer_length( &this->stereo_buf ); + blip_time_t clocks_emulated = (blargg_long) msec * this->clock_rate_ / 1000; + RETURN_ERR( run_clocks( this, &clocks_emulated ) ); + assert( clocks_emulated ); + Buffer_end_frame( &this->stereo_buf, clocks_emulated ); + } + } + return 0; +} + + +// Music emu + +blargg_err_t Hes_set_sample_rate( struct Hes_Emu* this, long rate ) +{ + require( !this->sample_rate_ ); // sample rate can't be changed once set + Buffer_init( &this->stereo_buf ); + RETURN_ERR( Buffer_set_sample_rate( &this->stereo_buf, rate, 1000 / 20 ) ); + + // Set bass frequency + Buffer_bass_freq( &this->stereo_buf, 60 ); + + this->sample_rate_ = rate; + return 0; +} + +void Sound_mute_voice( struct Hes_Emu* this, int index, bool mute ) +{ + require( (unsigned) index < (unsigned) this->voice_count_ ); + int bit = 1 << index; + int mask = this->mute_mask_ | bit; + if ( !mute ) + mask ^= bit; + Sound_mute_voices( this, mask ); +} + +void Sound_mute_voices( struct Hes_Emu* this, int mask ) +{ + require( this->sample_rate_ ); // sample rate must be set first + this->mute_mask_ = mask; + + // Set adpcm voice + struct channel_t ch = Buffer_channel( &this->stereo_buf ); + if ( mask & (1 << this->voice_count_ ) ) + Adpcm_set_output( &this->adpcm, 0, 0, 0, 0 ); + else + Adpcm_set_output( &this->adpcm, 0, ch.center, ch.left, ch.right ); + + // Set apu voices + int i = this->voice_count_ - 1; + for ( ; i--; ) + { + if ( mask & (1 << i) ) + { + Apu_osc_output( &this->apu, i, 0, 0, 0 ); + } + else + { + assert( (ch.center && ch.left && ch.right) || + (!ch.center && !ch.left && !ch.right) ); // all or nothing + Apu_osc_output( &this->apu, i, ch.center, ch.left, ch.right ); + } + } +} + +void Sound_set_tempo( struct Hes_Emu* this, double t ) +{ + require( this->sample_rate_ ); // sample rate must be set first + double const min = 0.02; + double const max = 4.00; + if ( t < min ) t = min; + if ( t > max ) t = max; + this->play_period = (hes_time_t) (period_60hz / t); + this->timer_base = (int) (1024 / t); + recalc_timer_load( this ); + this->tempo_ = t; +} + +void fill_buf( struct Hes_Emu* this ) ICODE_ATTR; +blargg_err_t Hes_start_track( struct Hes_Emu* this, int track ) +{ + clear_track_vars( this ); + + // Remap track if playlist available + if ( this->m3u.size > 0 ) { + struct entry_t* e = &this->m3u.entries[track]; + track = e->track; + } + + this->current_track_ = track; + + Buffer_clear( &this->stereo_buf ); + + memset( this->cpu.ram, 0, sizeof this->cpu.ram ); // some HES music relies on zero fill + memset( this->sgx, 0, sizeof this->sgx ); + + Apu_reset( &this->apu ); + Adpcm_reset( &this->adpcm ); + Cpu_reset( &this->cpu ); + + unsigned i; + for ( i = 0; i < sizeof this->header.banks; i++ ) + Cpu_set_mmr( this, i, this->header.banks [i] ); + Cpu_set_mmr( this, page_count, 0xFF ); // unmapped beyond end of address space + + this->irq.disables = timer_mask | vdp_mask; + this->irq.timer = future_hes_time; + this->irq.vdp = future_hes_time; + + this->timer.enabled = false; + this->timer.raw_load= 0x80; + this->timer.count = this->timer.load; + this->timer.fired = false; + this->timer.last_time = 0; + + this->vdp.latch = 0; + this->vdp.control = 0; + this->vdp.next_vbl = 0; + + this->cpu.ram [0x1FF] = (idle_addr - 1) >> 8; + this->cpu.ram [0x1FE] = (idle_addr - 1) & 0xFF; + this->cpu.r.sp = 0xFD; + this->cpu.r.pc = get_le16( this->header.init_addr ); + this->cpu.r.a = track; + + recalc_timer_load( this ); + this->last_frame_hook = 0; + + this->emu_track_ended_ = false; + this->track_ended = false; + + if ( !this->ignore_silence ) + { + // play until non-silence or end of track + long end; + for ( end =this-> max_initial_silence * stereo * this->sample_rate_; this->emu_time < end; ) + { + fill_buf( this ); + if ( this->buf_remain | (int) this->emu_track_ended_ ) + break; + } + + this->emu_time = this->buf_remain; + this->out_time = 0; + this->silence_time = 0; + this->silence_count = 0; + } + /* return track_ended() ? warning() : 0; */ + return 0; +} + +// Tell/Seek + +blargg_long msec_to_samples( blargg_long msec, long sample_rate ) +{ + blargg_long sec = msec / 1000; + msec -= sec * 1000; + return (sec * sample_rate + msec * sample_rate / 1000) * stereo; +} + +long Track_tell( struct Hes_Emu* this ) +{ + blargg_long rate = this->sample_rate_ * stereo; + blargg_long sec = this->out_time / rate; + return sec * 1000 + (this->out_time - sec * rate) * 1000 / rate; +} + +blargg_err_t Track_seek( struct Hes_Emu* this, long msec ) +{ + blargg_long time = msec_to_samples( msec, this->sample_rate_ ); + if ( time < this->out_time ) + RETURN_ERR( Hes_start_track( this, this->current_track_ ) ); + return Track_skip( this, time - this->out_time ); +} + +blargg_err_t skip_( struct Hes_Emu* this, long count ) ICODE_ATTR; +blargg_err_t skip_( struct Hes_Emu* this, long count ) +{ + // for long skip, mute sound + const long threshold = 30000; + if ( count > threshold ) + { + int saved_mute = this->mute_mask_; + Sound_mute_voices( this, ~0 ); + + while ( count > threshold / 2 && !this->emu_track_ended_ ) + { + RETURN_ERR( play_( this, buf_size, this->buf ) ); + count -= buf_size; + } + + Sound_mute_voices( this, saved_mute ); + } + + while ( count && !this->emu_track_ended_ ) + { + long n = buf_size; + if ( n > count ) + n = count; + count -= n; + RETURN_ERR( play_( this, n, this->buf ) ); + } + return 0; +} + +blargg_err_t Track_skip( struct Hes_Emu* this, long count ) +{ + require( this->current_track_ >= 0 ); // start_track() must have been called already + this->out_time += count; + + // remove from silence and buf first + { + long n = min( count, this->silence_count ); + this->silence_count -= n; + count -= n; + + n = min( count, this->buf_remain ); + this->buf_remain -= n; + count -= n; + } + + if ( count && !this->emu_track_ended_ ) + { + this->emu_time += count; + + // End track if error + if ( skip_( this, count ) ) + this->emu_track_ended_ = true; + } + + if ( !(this->silence_count | this->buf_remain) ) // caught up to emulator, so update track ended + this->track_ended |= this->emu_track_ended_; + + return 0; +} + + + +// Fading + +void Track_set_fade( struct Hes_Emu* this, long start_msec, long length_msec ) +{ + this->fade_step = this->sample_rate_ * length_msec / (fade_block_size * fade_shift * 1000 / stereo); + this->fade_start = msec_to_samples( start_msec, this->sample_rate_ ); +} + +// unit / pow( 2.0, (double) x / step ) +static int int_log( blargg_long x, int step, int unit ) ICODE_ATTR; +static int int_log( blargg_long x, int step, int unit ) +{ + int shift = x / step; + int fraction = (x - shift * step) * unit / step; + return ((unit - fraction) + (fraction >> 1)) >> shift; +} + +void handle_fade( struct Hes_Emu* this, long out_count, sample_t* out ) ICODE_ATTR; +void handle_fade( struct Hes_Emu* this, long out_count, sample_t* out ) +{ + int i; + for ( i = 0; i < out_count; i += fade_block_size ) + { + int const shift = 14; + int const unit = 1 << shift; + int gain = int_log( (this->out_time + i - this->fade_start) / fade_block_size, + this->fade_step, unit ); + if ( gain < (unit >> fade_shift) ) + this->track_ended = this->emu_track_ended_ = true; + + sample_t* io = &out [i]; + int count; + for ( count = min( fade_block_size, out_count - i ); count; --count ) + { + *io = (sample_t) ((*io * gain) >> shift); + ++io; + } + } +} + +// Silence detection + +void emu_play( struct Hes_Emu* this, long count, sample_t* out ) ICODE_ATTR; +void emu_play( struct Hes_Emu* this, long count, sample_t* out ) +{ + check( current_track_ >= 0 ); + this->emu_time += count; + if ( this->current_track_ >= 0 && !this->emu_track_ended_ ) { + + // End track if error + if ( play_( this, count, out ) ) + this->emu_track_ended_ = true; + } + else + memset( out, 0, count * sizeof *out ); +} + +// number of consecutive silent samples at end +static long count_silence( sample_t* begin, long size ) ICODE_ATTR; +static long count_silence( sample_t* begin, long size ) +{ + sample_t first = *begin; + *begin = silence_threshold; // sentinel + sample_t* p = begin + size; + while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { } + *begin = first; + return size - (p - begin); +} + +// fill internal buffer and check it for silence +void fill_buf( struct Hes_Emu* this ) +{ + assert( !this->buf_remain ); + if ( !this->emu_track_ended_ ) + { + emu_play( this, buf_size, this->buf ); + long silence = count_silence( this->buf, buf_size ); + if ( silence < buf_size ) + { + this->silence_time = this->emu_time - silence; + this->buf_remain = buf_size; + return; + } + } + this->silence_count += buf_size; +} + +blargg_err_t Hes_play( struct Hes_Emu* this, long out_count, sample_t* out ) +{ + if ( this->track_ended ) + { + memset( out, 0, out_count * sizeof *out ); + } + else + { + require( this->current_track_ >= 0 ); + require( out_count % stereo == 0 ); + + assert( this->emu_time >= this->out_time ); + + // prints nifty graph of how far ahead we are when searching for silence + //dprintf( "%*s \n", int ((emu_time - out_time) * 7 / sample_rate()), "*" ); + + long pos = 0; + if ( this->silence_count ) + { + // during a run of silence, run emulator at >=2x speed so it gets ahead + long ahead_time = this->silence_lookahead * (this->out_time + out_count - this->silence_time) + this->silence_time; + while ( this->emu_time < ahead_time && !(this->buf_remain | this->emu_track_ended_) ) + fill_buf( this ); + + // fill with silence + pos = min( this->silence_count, out_count ); + memset( out, 0, pos * sizeof *out ); + this->silence_count -= pos; + + if ( this->emu_time - this->silence_time > silence_max * stereo * this->sample_rate_ ) + { + this->track_ended = this->emu_track_ended_ = true; + this->silence_count = 0; + this->buf_remain = 0; + } + } + + if ( this->buf_remain ) + { + // empty silence buf + long n = min( this->buf_remain, out_count - pos ); + memcpy( &out [pos], this->buf + (buf_size - this->buf_remain), n * sizeof *out ); + this->buf_remain -= n; + pos += n; + } + + // generate remaining samples normally + long remain = out_count - pos; + if ( remain ) + { + emu_play( this, remain, out + pos ); + this->track_ended |= this->emu_track_ended_; + + if ( !this->ignore_silence || this->out_time > this->fade_start ) + { + // check end for a new run of silence + long silence = count_silence( out + pos, remain ); + if ( silence < remain ) + this->silence_time = this->emu_time - silence; + + if ( this->emu_time - this->silence_time >= buf_size ) + fill_buf( this ); // cause silence detection on next play() + } + } + + if ( this->out_time > this->fade_start ) + handle_fade( this, out_count, out ); + } + this->out_time += out_count; + return 0; +} diff --git a/apps/codecs/libgme/hes_emu.h b/apps/codecs/libgme/hes_emu.h new file mode 100644 index 0000000000..18dbe0d506 --- /dev/null +++ b/apps/codecs/libgme/hes_emu.h @@ -0,0 +1,229 @@ +// TurboGrafx-16/PC Engine HES music file emulator + +// Game_Music_Emu 0.5.2 +#ifndef HES_EMU_H +#define HES_EMU_H + +#include "blargg_source.h" + +#include "multi_buffer.h" +#include "rom_data.h" +#include "hes_apu.h" +#include "hes_apu_adpcm.h" +#include "hes_cpu.h" +#include "m3u_playlist.h" + +typedef short sample_t; + +enum { buf_size = 2048 }; + +// HES file header +enum { header_size = 0x20 }; +struct header_t +{ + byte tag [4]; + byte vers; + byte first_track; + byte init_addr [2]; + byte banks [8]; + byte data_tag [4]; + byte size [4]; + byte addr [4]; + byte unused [4]; +}; + + +struct timer_t { + hes_time_t last_time; + blargg_long count; + blargg_long load; + int raw_load; + byte enabled; + byte fired; +}; + +struct vdp_t { + hes_time_t next_vbl; + byte latch; + byte control; +}; + +struct irq_t { + hes_time_t timer; + hes_time_t vdp; + byte disables; +}; + + +struct Hes_Emu { + hes_time_t play_period; + hes_time_t last_frame_hook; + int timer_base; + + struct timer_t timer; + struct vdp_t vdp; + struct irq_t irq; + + // Sound + long clock_rate_; + long sample_rate_; + unsigned buf_changed_count; + int voice_count_; + double tempo_; + double gain_; + + // track-specific + byte track_count; + volatile bool track_ended; + int current_track_; + blargg_long out_time; // number of samples played since start of track + blargg_long emu_time; // number of samples emulator has generated since start of track + bool emu_track_ended_; // emulator has reached end of track + + // fading + blargg_long fade_start; + int fade_step; + + // silence detection + // Disable automatic end-of-track detection and skipping of silence at beginning + bool ignore_silence; + + int max_initial_silence; + int mute_mask_; + int silence_lookahead; // speed to run emulator when looking ahead for silence + long silence_time; // number of samples where most recent silence began + long silence_count; // number of samples of silence to play before using buf + long buf_remain; // number of samples left in silence buffer + + // Larger files at the end + // Header for currently loaded file + struct header_t header; + + // M3u Playlist + struct M3u_Playlist m3u; + + // Hes Cpu + byte* write_pages [page_count + 1]; // 0 if unmapped or I/O space + struct Hes_Cpu cpu; + + struct Hes_Apu apu; + struct Hes_Apu_Adpcm adpcm; + + struct Stereo_Buffer stereo_buf; + sample_t buf [buf_size]; + + // rom & ram + struct Rom_Data rom; + byte sgx [3 * page_size + cpu_padding]; +}; + + +// Basic functionality +// Initializes Hes_Emu structure +void Hes_init( struct Hes_Emu* this ); + +// Stops (clear) Hes_Emu structure +void Hes_stop( struct Hes_Emu* this ); + +// Loads a file from memory +blargg_err_t Hes_load( struct Hes_Emu* this, void* data, long size ); + +// Set output sample rate. Must be called only once before loading file. +blargg_err_t Hes_set_sample_rate( struct Hes_Emu* this, long sample_rate ); + +// Start a track, where 0 is the first track. Also clears warning string. +blargg_err_t Hes_start_track( struct Hes_Emu* this, int ); + +// Generate 'count' samples info 'buf'. Output is in stereo. Any emulation +// errors set warning string, and major errors also end track. +blargg_err_t Hes_play( struct Hes_Emu* this, long count, sample_t* buf ) ICODE_ATTR; + +// Track status/control +// Number of milliseconds (1000 msec = 1 second) played since ning of track +long Track_tell( struct Hes_Emu* this ); + +// Seek to new time in track. Seeking backwards or far forward can take a while. +blargg_err_t Track_seek( struct Hes_Emu* this, long msec ); + +// Skip n samples +blargg_err_t Track_skip( struct Hes_Emu* this, long n ); + +// Set start time and length of track fade out. Once fade ends track_ended() returns +// true. Fade time can be changed while track is playing. +void Track_set_fade( struct Hes_Emu* this, long start_msec, long length_msec ); + +// Get track length in milliseconds +static inline long Track_get_length( struct Hes_Emu* this, int n ) +{ + long length = 120 * 1000; /* 2 minutes */ + if ( (this->m3u.size > 0) && (n < this->m3u.size) ) { + struct entry_t* entry = &this->m3u.entries [n]; + length = entry->length; + } + + return length; +} + + +// Sound customization +// Adjust song tempo, where 1.0 = normal, 0.5 = half speed, 2.0 = double speed. +// Track length as returned by track_info() assumes a tempo of 1.0. +void Sound_set_tempo( struct Hes_Emu* this, double ); + +// Mute/unmute voice i, where voice 0 is first voice +void Sound_mute_voice( struct Hes_Emu* this, int index, bool mute ); + +// Set muting state of all voices at once using a bit mask, where -1 mutes them all, +// 0 unmutes them all, 0x01 mutes just the first voice, etc. +void Sound_mute_voices( struct Hes_Emu* this, int mask ); + +// Change overall output amplitude, where 1.0 results in minimal clamping. +// Must be called before set_sample_rate(). +static inline void Sound_set_gain( struct Hes_Emu* this, double g ) +{ + assert( !this->sample_rate_ ); // you must set gain before setting sample rate + this->gain_ = g; +} + + +// Emulation (You shouldn't touch these) + +int Cpu_read( struct Hes_Emu* this, hes_addr_t ) ICODE_ATTR; +void Cpu_write( struct Hes_Emu* this, hes_addr_t, int ) ICODE_ATTR; +void Cpu_write_vdp( struct Hes_Emu* this, int addr, int data ) ICODE_ATTR; +int Cpu_done( struct Hes_Emu* this ) ICODE_ATTR; + +int Emu_cpu_read( struct Hes_Emu* this, hes_addr_t ) ICODE_ATTR; +void Emu_cpu_write( struct Hes_Emu* this, hes_addr_t, int data ) ICODE_ATTR; + +static inline byte const* Emu_cpu_set_mmr( struct Hes_Emu* this, int page, int bank ) +{ + this->write_pages [page] = 0; + if ( bank < 0x80 ) + return Rom_at_addr( &this->rom, bank * (blargg_long) page_size ); + + byte* data = 0; + switch ( bank ) + { + case 0xF8: + data = this->cpu.ram; + break; + + case 0xF9: + case 0xFA: + case 0xFB: + data = &this->sgx [(bank - 0xF9) * page_size]; + break; + + default: + if ( bank != 0xFF ) { + dprintf( "Unmapped bank $%02X\n", bank ); + } + return this->rom.unmapped; + } + + this->write_pages [page] = data; + return data; +} + +#endif diff --git a/apps/codecs/libgme/inflate/bbfuncs.c b/apps/codecs/libgme/inflate/bbfuncs.c new file mode 100644 index 0000000000..3b23c3b6db --- /dev/null +++ b/apps/codecs/libgme/inflate/bbfuncs.c @@ -0,0 +1,147 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "bbfuncs.h" + +#if defined(ROCKBOX) +#include "codeclib.h" +#endif + +void error_die(const char* msg) +{ + (void)msg; +} + +void error_msg(const char* msg) +{ + (void)msg; +} + +size_t safe_read(struct mbreader_t *md, void *buf, size_t count) +{ + ssize_t n; + + do { + n = mbread(md, buf, count); + } while (n < 0&&n!=-1); + + return n; +} + +/* + * Read all of the supplied buffer from a file This does multiple reads as + *necessary. Returns the amount read, or -1 on an error. A short read is + *returned on an end of file. + */ +ssize_t full_read(struct mbreader_t *md, void *buf, size_t len) +{ + ssize_t cc; + ssize_t total; + + total = 0; + + while (len) + { + cc = safe_read(md, buf, len); + + if (cc < 0) + return cc; /* read() returns -1 on failure. */ + + if (cc == 0) + break; + + buf = ((char *)buf) + cc; + total += cc; + len -= cc; + } + + return total; +} + +/* Die with an error message if we can't read the entire buffer. */ +void xread(struct mbreader_t *md, void *buf, ssize_t count) +{ + if (count) + { + ssize_t size = full_read(md, buf, count); + if (size != count) + error_die("short read"); + } +} + +/* Die with an error message if we can't read one character. */ +unsigned char xread_char(struct mbreader_t *md) +{ + unsigned char tmp; + + xread(md, &tmp, 1); + + return tmp; +} + +void check_header_gzip(struct mbreader_t *src_md) +{ + union { + unsigned char raw[8]; + struct { + unsigned char method; + unsigned char flags; + unsigned int mtime; + unsigned char xtra_flags; + unsigned char os_flags; + } formatted; + } header; + + xread(src_md, header.raw, 8); + + /* Check the compression method */ + if (header.formatted.method != 8) + error_die("Unknown compression method"); + + if (header.formatted.flags & 0x04) + { + /* bit 2 set: extra field present */ + unsigned char extra_short; + + extra_short = xread_char(src_md) + (xread_char(src_md) << 8); + while (extra_short > 0) + { + /* Ignore extra field */ + xread_char(src_md); + extra_short--; + } + } + + /* Discard original name if any */ + if (header.formatted.flags & 0x08) + /* bit 3 set: original file name present */ + while(xread_char(src_md) != 0) ; + + /* Discard file comment if any */ + if (header.formatted.flags & 0x10) + /* bit 4 set: file comment present */ + while(xread_char(src_md) != 0) ; + + /* Read the header checksum */ + if (header.formatted.flags & 0x02) + { + xread_char(src_md); + xread_char(src_md); + } +} diff --git a/apps/codecs/libgme/inflate/bbfuncs.h b/apps/codecs/libgme/inflate/bbfuncs.h new file mode 100644 index 0000000000..fe03ec1a3c --- /dev/null +++ b/apps/codecs/libgme/inflate/bbfuncs.h @@ -0,0 +1,33 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef BBFUNCS_H +#define BBFUNCS_H + +#include "mbreader.h" + +void error_die(const char* msg); +void error_msg(const char* msg); +size_t safe_read(struct mbreader_t *md, void *buf, size_t count); +ssize_t full_read(struct mbreader_t *md, void *buf, size_t len); +void xread(struct mbreader_t *md, void *buf, ssize_t count); +unsigned char xread_char(struct mbreader_t *md); +void check_header_gzip(struct mbreader_t *md); + +#endif diff --git a/apps/codecs/libgme/inflate/inflate.c b/apps/codecs/libgme/inflate/inflate.c new file mode 100644 index 0000000000..807dee302e --- /dev/null +++ b/apps/codecs/libgme/inflate/inflate.c @@ -0,0 +1,1159 @@ +/* + * gunzip implementation for wikiviewer (c) Frederik M.J.V., 2006. + * some bug fixes by Adam Gashlin gunzip implementation for busybox + * + * Based on GNU gzip v1.2.4 Copyright (C) 1992-1993 Jean-loup Gailly. + * + * Originally adjusted for busybox by Sven Rudolph + * based on gzip sources + * + * Adjusted further by Erik Andersen to support files as + *well as stdin/stdout, and to generally behave itself wrt command line + *handling. + * + * General cleanup to better adhere to the style guide and make use of standard + *busybox functions by Glenn McGrath + * + * read_gz interface + associated hacking by Laurence Anderson + * + * Fixed huft_build() so decoding end-of-block code does not grab more bits than + *necessary (this is required by unzip applet), added inflate_cleanup() to free + *leaked bytebuffer memory (used in unzip.c), and some minor style guide + *cleanups by Ed Clark + * + * gzip (GNU zip) -- compress files with zip algorithm and 'compress' interface + *Copyright (C) 1992-1993 Jean-loup Gailly The unzip code was written and put in + *the public domain by Mark Adler. Portions of the lzw code are derived from the + *public domain 'compress' + * written by Spencer Thomas, Joe Orost, James Woods, Jim McKie, Steve Davies, + *Ken Turkowski, Dave Mack and Peter Jannesen. + * + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#ifndef NULL +#define NULL 0 +#endif +#define ENABLE_DESKTOP 0 +#define USE_DESKTOP(...) +#include "mallocer.h" +#include "bbfuncs.h" +#include "inflate.h" +#include "mallocer.h" + +#define TRIM_FILE_ON_ERROR 1 + +typedef struct huft_s { + unsigned char e; /* number of extra bits or operation */ + unsigned char b; /* number of bits in this code or subcode */ + union { + unsigned short n; /* literal, length base, or distance base */ + struct huft_s *t; /* pointer to next level of table */ + } v; +} huft_t; + +/*static void *mainmembuf;*/ +static void *huftbuffer1; +static void *huftbuffer2; + +#define HUFT_MMP1 8 +#define HUFT_MMP2 9 + +static struct mbreader_t *gunzip_src_md; +static unsigned int gunzip_bytes_out; /* number of output bytes */ +static unsigned int gunzip_outbuf_count; /* bytes in output buffer */ + +/* gunzip_window size--must be a power of two, and at least 32K for zip's + deflate method */ +enum { + gunzip_wsize = 0x8000 +}; + +static unsigned char *gunzip_window; +static uint32_t ifl_total; + +static uint32_t gunzip_crc; + +/* If BMAX needs to be larger than 16, then h and x[] should be ulg. */ +#define BMAX 16 /* maximum bit length of any code (16 for explode) */ +#define N_MAX 288 /* maximum number of codes in any set */ + +/* bitbuffer */ +static unsigned int gunzip_bb; /* bit buffer */ +static unsigned char gunzip_bk; /* bits in bit buffer */ + +/* These control the size of the bytebuffer */ +static unsigned int bytebuffer_max = 0x8000; +static unsigned char *bytebuffer = NULL; +static unsigned int bytebuffer_offset = 0; +static unsigned int bytebuffer_size = 0; + +static const unsigned short mask_bits[] = { + 0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, + 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff +}; + +/* Copy lengths for literal codes 257..285 */ +static const unsigned short cplens[] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 +}; + +/* note: see note #13 above about the 258 in this list. */ +/* Extra bits for literal codes 257..285 */ +static const unsigned char cplext[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, + 5, 5, 5, 0, 99, 99 +}; /* 99==invalid */ + +/* Copy offsets for distance codes 0..29 */ +static const unsigned short cpdist[] = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, + 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 +}; + +/* Extra bits for distance codes */ +static const unsigned char cpdext[] = { + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, + 11, 11, 12, 12, 13, 13 +}; + +/* Tables for deflate from PKZIP's appnote.txt. */ +/* Order of the bit length code lengths */ +static const unsigned char border[] = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 +}; + +static const uint32_t crc_table[256]= { + 0,1996959894,-301047508,-1727442502,124634137,1886057615, + -379345611,-1637575261,249268274,2044508324,-522852066, + -1747789432,162941995,2125561021,-407360249,-1866523247, + 498536548,1789927666,-205950648,-2067906082,450548861, + 1843258603,-187386543,-2083289657,325883990,1684777152, + -43845254,-1973040660,335633487,1661365465,-99664541, + -1928851979,997073096,1281953886,-715111964,-1570279054, + 1006888145,1258607687,-770865667,-1526024853,901097722, + 1119000684,-608450090,-1396901568,853044451,1172266101, + -589951537,-1412350631,651767980,1373503546,-925412992, + -1076862698,565507253,1454621731,-809855591,-1195530993, + 671266974,1594198024,-972236366,-1324619484,795835527, + 1483230225,-1050600021,-1234817731,1994146192,31158534, + -1731059524,-271249366,1907459465,112637215,-1614814043, + -390540237,2013776290,251722036,-1777751922,-519137256, + 2137656763,141376813,-1855689577,-429695999,1802195444, + 476864866,-2056965928,-228458418,1812370925,453092731, + -2113342271,-183516073,1706088902,314042704,-1950435094, + -54949764,1658658271,366619977,-1932296973,-69972891, + 1303535960,984961486,-1547960204,-725929758,1256170817, + 1037604311,-1529756563,-740887301,1131014506,879679996, + -1385723834,-631195440,1141124467,855842277,-1442165665, + -586318647,1342533948,654459306,-1106571248,-921952122, + 1466479909,544179635,-1184443383,-832445281,1591671054, + 702138776,-1328506846,-942167884,1504918807,783551873, + -1212326853,-1061524307,-306674912,-1698712650,62317068, + 1957810842,-355121351,-1647151185,81470997,1943803523, + -480048366,-1805370492,225274430,2053790376,-468791541, + -1828061283,167816743,2097651377,-267414716,-2029476910, + 503444072,1762050814,-144550051,-2140837941,426522225, + 1852507879,-19653770,-1982649376,282753626,1742555852, + -105259153,-1900089351,397917763,1622183637,-690576408, + -1580100738,953729732,1340076626,-776247311,-1497606297, + 1068828381,1219638859,-670225446,-1358292148,906185462, + 1090812512,-547295293,-1469587627,829329135,1181335161, + -882789492,-1134132454,628085408,1382605366,-871598187, + -1156888829,570562233,1426400815,-977650754,-1296233688, + 733239954,1555261956,-1026031705,-1244606671,752459403, + 1541320221,-1687895376,-328994266,1969922972,40735498, + -1677130071,-351390145,1913087877,83908371,-1782625662, + -491226604,2075208622,213261112,-1831694693,-438977011, + 2094854071,198958881,-2032938284,-237706686,1759359992, + 534414190,-2118248755,-155638181,1873836001,414664567, + -2012718362,-15766928,1711684554,285281116,-1889165569, + -127750551,1634467795,376229701,-1609899400,-686959890, + 1308918612,956543938,-1486412191,-799009033,1231636301, + 1047427035,-1362007478,-640263460,1088359270,936918000, + -1447252397,-558129467,1202900863,817233897,-1111625188, + -893730166,1404277552,615818150,-1160759803,-841546093, + 1423857449,601450431,-1285129682,-1000256840,1567103746, + 711928724,-1274298825,-1022587231,1510334235,755167117 +}; + +static unsigned int fill_bitbuffer(unsigned int bitbuffer, unsigned int *current, + const unsigned int required) +{ + while (*current < required) + { + if (bytebuffer_offset >= bytebuffer_size) + { + /* Leave the first 4 bytes empty so we can always unwind the + bitbuffer to the front of the bytebuffer, leave 4 bytes free at + end of tail so we can easily top up buffer in + check_trailer_gzip() */ + if (1 > (bytebuffer_size = safe_read(gunzip_src_md, &bytebuffer[4], + bytebuffer_max - 8))) + error_die("unexpected end of file"); + + bytebuffer_size += 4; + bytebuffer_offset = 4; + } + + bitbuffer |= ((unsigned int) bytebuffer[bytebuffer_offset]) << *current; + bytebuffer_offset++; + *current += 8; + } + return(bitbuffer); +} + +/* + * Free the malloc'ed tables built by huft_build(), which makes a linked list of + *the tables it made, with the links in a dummy first entry of each table. + * t: table to free + */ +static int huft_free(huft_t * t,unsigned char bufnum) +{ + wpw_reset_mempool(bufnum); + if(t==0) + { + } + + return 0; +} + +/* Given a list of code lengths and a maximum table size, make a set of tables + to decode that set of codes. Return zero on success, one if the given code + set is incomplete (the tables are still built in this case), two if the input + is invalid (all zero length codes or an oversubscribed set of lengths), and + three if not enough memory. + * + * b: code lengths in bits (all assumed <= BMAX) n: number of codes + *(assumed <= N_MAX) s: number of simple-valued codes (0..s-1) d: list of + *base values for non-simple codes e: list of extra bits for non-simple codes + *t: result: starting table m: maximum lookup bits, returns actual bufnum: + *the number of the memory pool to fetch memory from + */ +static +int huft_build(unsigned int *b, const unsigned int n, + const unsigned int s, const unsigned short *d, + const unsigned char *e, huft_t ** t, unsigned int *m, + unsigned char bufnum) +{ + unsigned a=0; /* counter for codes of length k */ + unsigned c[BMAX + 1]; /* bit length count table */ + unsigned eob_len=0; /* length of end-of-block code (value 256) */ + unsigned f=0; /* i repeats in table every f entries */ + int g=0; /* maximum code length */ + int htl=0; /* table level */ + unsigned i=0; /* counter, current code */ + unsigned j=0; /* counter */ + int k=0; /* number of bits in current code */ + unsigned *p; /* pointer into c[], b[], or v[] */ + huft_t *q; /* points to current table */ + huft_t r; /* table entry for structure assignment */ + huft_t *u[BMAX]; /* table stack */ + unsigned v[N_MAX]; /* values in order of bit length */ + int ws[BMAX+1]; /* bits decoded stack */ + int w=0; /* bits decoded */ + unsigned x[BMAX + 1]; /* bit offsets, then code stack */ + unsigned *xp; /* pointer into x */ + int y=0; /* number of dummy codes added */ + unsigned z=0; /* number of entries in current table */ + + /* Length of EOB code, if any */ + eob_len = n > 256 ? b[256] : BMAX; + + /* Generate counts for each bit length */ + memset((void *)c, 0, sizeof(c)); + p = b; + i = n; + do { + c[*p]++; /* assume all entries <= BMAX */ + p++; /* Can't combine with above line (Solaris bug) */ + } while (--i); + if (c[0] == n) /* null input--all zero length codes */ + { + *t = (huft_t *) NULL; + *m = 0; + return 2; + } + + /* Find minimum and maximum length, bound *m by those */ + for (j = 1; (c[j] == 0) && (j <= BMAX); j++) ; + + k = j; /* minimum code length */ + for (i = BMAX; (c[i] == 0) && i; i--) ; + + g = i; /* maximum code length */ + *m = (*m < j) ? j : ((*m > i) ? i : *m); + + /* Adjust last length count to fill out codes, if needed */ + for (y = 1 << j; j < i; j++, y <<= 1) + { + if ((y -= c[j]) < 0) + return 2; /* bad input: more codes than bits */ + } + + if ((y -= c[i]) < 0) + return 2; + + c[i] += y; + + /* Generate starting offsets into the value table for each length */ + x[1] = j = 0; + p = c + 1; + xp = x + 2; + while (--i) /* note that i == g from above */ + { + *xp++ = (j += *p++); + } + + /* Make a table of values in order of bit lengths */ + p = b; + i = 0; + do { + if ((j = *p++) != 0) + v[x[j]++] = i; + } while (++i < n); + + /* Generate the Huffman codes and for each, make the table entries */ + x[0] = i = 0; /* first Huffman code is zero */ + p = v; /* grab values in bit order */ + htl = -1; /* no tables yet--level -1 */ + w = ws[0] = 0; /* bits decoded */ + u[0] = (huft_t *) NULL; /* just to keep compilers happy */ + q = (huft_t *) NULL; /* ditto */ + z = 0; /* ditto */ + + /* go through the bit lengths (k already is bits in shortest code) */ + for (; k <= g; k++) + { + a = c[k]; + while (a--) + { + /* here i is the Huffman code of length k bits for value *p */ + /* make tables up to required level */ + while (k > ws[htl + 1]) + { + w = ws[++htl]; + + /* compute minimum size table less than or equal to *m bits */ + z = (z = g - w) > *m ? *m : z; /* upper limit on table size */ + if ((f = 1 << (j = k - w)) > a + 1) /* try a k-w bit table */ + { /* too few codes for k-w bit table */ + f -= a + 1; /* deduct codes from patterns left */ + xp = c + k; + while (++j < z) /* try smaller tables up to z bits */ + { + if ((f <<= 1) <= *++xp) + break; /* enough codes to use up j bits */ + + f -= *xp; /* else deduct codes from patterns */ + } + } + + j = ((unsigned)(w + j) > eob_len && (unsigned)w < eob_len) + ? eob_len - w : j; /* make EOB code end at table */ + z = 1 << j; /* table entries for j-bit table */ + ws[htl+1] = w + j; /* set bits decoded in stack */ + + /* allocate and link in new table */ + q = (huft_t *) wpw_malloc(bufnum,(z + 1) * sizeof(huft_t)); + if(q==0) + return 3; + + *t = q + 1; /* link to list for huft_free() */ + t = &(q->v.t); + u[htl] = ++q; /* table starts after link */ + + /* connect to last table, if there is one */ + if (htl) + { + x[htl] = i; /* save pattern for backing up */ + + /* bits to dump before this table */ + r.b = (unsigned char) (w - ws[htl - 1]); + r.e = (unsigned char) (16 + j); /* bits in this table */ + r.v.t = q; /* pointer to this table */ + j = (i & ((1 << w) - 1)) >> ws[htl - 1]; + u[htl - 1][j] = r; /* connect to last table */ + } + } + + /* set up table entry in r */ + r.b = (unsigned char) (k - w); + if (p >= v + n) + r.e = 99; /* out of values--invalid code */ + else if (*p < s) + { + r.e = (unsigned char) (*p < 256 ? 16 : 15); /* 256 is EOB + code */ + r.v.n = (unsigned short) (*p++); /* simple code is just the + value */ + } + else + { + r.e = (unsigned char) e[*p - s]; /* non-simple--look up in lists + */ + r.v.n = d[*p++ - s]; + } + + /* fill code-like entries with r */ + f = 1 << (k - w); + for (j = i >> w; j < z; j += f) + { + q[j] = r; + } + + /* backwards increment the k-bit code i */ + for (j = 1 << (k - 1); i &j; j >>= 1) + { + i ^= j; + } + i ^= j; + + /* backup over finished tables */ + while ((i & ((1 << w) - 1)) != x[htl]) + { + w = ws[--htl]; + } + } + } + + /* return actual size of base table */ + *m = ws[1]; + + /* Return true (1) if we were given an incomplete table */ + return y != 0 && g != 1; +} + +/* + * inflate (decompress) the codes in a deflated (compressed) block. Return an + *error code or zero if it all goes ok. + * + * tl, td: literal/length and distance decoder tables bl, bd: number of bits + *decoded by tl[] and td[] + */ +static int inflate_codes_resumeCopy = 0; +static int inflate_codes(huft_t * my_tl, huft_t * my_td, + const unsigned int my_bl, const unsigned int my_bd, + int setup) +{ + static unsigned int e; /* table entry flag/number of extra bits */ + static unsigned int n, d; /* length and index for copy */ + static unsigned int w; /* current gunzip_window position */ + static huft_t *t; /* pointer to table entry */ + static unsigned int ml, md; /* masks for bl and bd bits */ + static unsigned int b; /* bit buffer */ + static unsigned int k; /* number of bits in bit buffer */ + static huft_t *tl, *td; + static unsigned int bl, bd; + + if (setup) /* 1st time we are called, copy in variables */ + { + tl = my_tl; + td = my_td; + bl = my_bl; + bd = my_bd; + /* make local copies of globals */ + b = gunzip_bb; /* initialize bit buffer */ + k = gunzip_bk; + w = gunzip_outbuf_count; /* initialize gunzip_window position + */ + + /* inflate the coded data */ + ml = mask_bits[bl]; /* precompute masks for speed */ + md = mask_bits[bd]; + return 0; /* Don't actually do anything the first time */ + } + + if (inflate_codes_resumeCopy) goto do_copy; + + while (1) /* do until end of block */ + { + b = fill_bitbuffer(b, &k, bl); + if ((e = (t = tl + ((unsigned) b & ml))->e) > 16) + do { + if (e == 99) + error_die("inflate_codes error 1"); + + b >>= t->b; + k -= t->b; + e -= 16; + b = fill_bitbuffer(b, &k, e); + } while ((e = + (t = t->v.t + ((unsigned) b & mask_bits[e]))->e) > 16); + + b >>= t->b; + k -= t->b; + if (e == 16) /* then it's a literal */ + { + gunzip_window[w++] = (unsigned char) t->v.n; + if (w == gunzip_wsize) + { + gunzip_outbuf_count = (w); + w = 0; + return 1; /* We have a block to read */ + } + } + else /* it's an EOB or a length */ + { /* exit if end of block */ + if (e == 15) + break; + + /* get length of block to copy */ + b = fill_bitbuffer(b, &k, e); + n = t->v.n + ((unsigned) b & mask_bits[e]); + b >>= e; + k -= e; + + /* decode distance of block to copy */ + b = fill_bitbuffer(b, &k, bd); + if ((e = (t = td + ((unsigned) b & md))->e) > 16) + do { + if (e == 99) + error_die("inflate_codes error 2"); + + b >>= t->b; + k -= t->b; + e -= 16; + b = fill_bitbuffer(b, &k, e); + } while ((e = + (t = + t->v.t + ((unsigned) b & mask_bits[e]))->e) > 16); + + b >>= t->b; + k -= t->b; + b = fill_bitbuffer(b, &k, e); + d = w - t->v.n - ((unsigned) b & mask_bits[e]); + b >>= e; + k -= e; + + /* do the copy */ +do_copy: do { + n -= (e = + (e = + gunzip_wsize - ((d &= gunzip_wsize - 1) > w ? d : w)) > n ? n : e); + /* copy to new buffer to prevent possible overwrite */ + if (w - d >= e) /* (this test assumes unsigned comparison) + */ + { + memcpy(gunzip_window + w, gunzip_window + d, e); + w += e; + d += e; + } + else + { + /* do it slow to avoid memcpy() overlap */ + /* !NOMEMCPY */ + do { + gunzip_window[w++] = gunzip_window[d++]; + } while (--e); + } + + if (w == gunzip_wsize) + { + gunzip_outbuf_count = (w); + if (n) inflate_codes_resumeCopy = 1; + else inflate_codes_resumeCopy = 0; + + w = 0; + return 1; + } + } while (n); + inflate_codes_resumeCopy = 0; + } + } + + /* restore the globals from the locals */ + gunzip_outbuf_count = w; /* restore global gunzip_window pointer */ + gunzip_bb = b; /* restore global bit buffer */ + gunzip_bk = k; + + /* normally just after call to inflate_codes, but save code by putting it + here */ + /* free the decoding tables, return */ + huft_free(tl,HUFT_MMP1); + huft_free(td,HUFT_MMP2); + + /* done */ + return 0; +} + +static int inflate_stored(int my_n, int my_b_stored, int my_k_stored, int setup) +{ + static unsigned int n, b_stored, k_stored, w; + if (setup) + { + n = my_n; + b_stored = my_b_stored; + k_stored = my_k_stored; + w = gunzip_outbuf_count; /* initialize gunzip_window position */ + return 0; /* Don't do anything first time */ + } + + /* read and output the compressed data */ + while (n--) + { + b_stored = fill_bitbuffer(b_stored, &k_stored, 8); + gunzip_window[w++] = (unsigned char) b_stored; + if (w == gunzip_wsize) + { + gunzip_outbuf_count = (w); + w = 0; + b_stored >>= 8; + k_stored -= 8; + return 1; /* We have a block */ + } + + b_stored >>= 8; + k_stored -= 8; + } + + /* restore the globals from the locals */ + gunzip_outbuf_count = w; /* restore global gunzip_window pointer */ + gunzip_bb = b_stored; /* restore global bit buffer */ + gunzip_bk = k_stored; + return 0; /* Finished */ +} + +/* + * decompress an inflated block e: last block flag + * + * GLOBAL VARIABLES: bb, kk, + */ +/* Return values: -1 = inflate_stored, -2 = inflate_codes */ +static int inflate_block(int *e) +{ + unsigned t; /* block type */ + unsigned int b; /* bit buffer */ + unsigned int k; /* number of bits in bit buffer */ + + /* make local bit buffer */ + + b = gunzip_bb; + k = gunzip_bk; + + /* read in last block bit */ + b = fill_bitbuffer(b, &k, 1); + *e = (int) b & 1; + b >>= 1; + k -= 1; + + /* read in block type */ + b = fill_bitbuffer(b, &k, 2); + t = (unsigned) b & 3; + b >>= 2; + k -= 2; + + /* restore the global bit buffer */ + gunzip_bb = b; + gunzip_bk = k; + + /* inflate that block type */ + switch (t) + { + case 0: /* Inflate stored */ + { + unsigned int n=0; /* number of bytes in block */ + unsigned int b_stored=0; /* bit buffer */ + unsigned int k_stored=0; /* number of bits in bit buffer */ + + /* make local copies of globals */ + b_stored = gunzip_bb; /* initialize bit buffer */ + k_stored = gunzip_bk; + + /* go to byte boundary */ + n = k_stored & 7; + b_stored >>= n; + k_stored -= n; + + /* get the length and its complement */ + b_stored = fill_bitbuffer(b_stored, &k_stored, 16); + n = ((unsigned) b_stored & 0xffff); + b_stored >>= 16; + k_stored -= 16; + + b_stored = fill_bitbuffer(b_stored, &k_stored, 16); + if (n != (unsigned) ((~b_stored) & 0xffff)) + return 1; /* error in compressed data */ + + b_stored >>= 16; + k_stored -= 16; + + inflate_stored(n, b_stored, k_stored, 1); /* Setup inflate_stored */ + return -1; + } + case 1: /* Inflate fixed decompress an inflated type 1 (fixed + Huffman codes) block. We should either replace this + with a custom decoder, or at least precompute the + Huffman tables. + */ + { + int i; /* temporary variable */ + huft_t *tl; /* literal/length code table */ + huft_t *td; /* distance code table */ + unsigned int bl; /* lookup bits for tl */ + unsigned int bd; /* lookup bits for td */ + unsigned int l[288]; /* length list for huft_build */ + + /* set up literal table */ + for (i = 0; i < 144; i++) + { + l[i] = 8; + } + for (; i < 256; i++) + { + l[i] = 9; + } + for (; i < 280; i++) + { + l[i] = 7; + } + for (; i < 288; i++) /* make a complete, but wrong code set */ + { + l[i] = 8; + } + bl = 7; + if ((i = huft_build(l, 288, 257, cplens, cplext, &tl, &bl,HUFT_MMP1)) != 0) + return i; + + /* set up distance table */ + for (i = 0; i < 30; i++) /* make an incomplete code set */ + { + l[i] = 5; + } + bd = 5; + if ((i = huft_build(l, 30, 0, cpdist, cpdext, &td, &bd,HUFT_MMP2)) > 1) + { + huft_free(tl,HUFT_MMP1); + return i; + } + + /* decompress until an end-of-block code */ + inflate_codes(tl, td, bl, bd, 1); /* Setup inflate_codes */ + + /* huft_free code moved into inflate_codes */ + + return -2; + } + case 2: /* Inflate dynamic */ + { + const int dbits = 6; /* bits in base distance lookup table */ + const int lbits = 9; /* bits in base literal/length lookup table */ + + huft_t *tl; /* literal/length code table */ + huft_t *td; /* distance code table */ + unsigned int i; /* temporary variables */ + unsigned int j; + unsigned int l; /* last length */ + unsigned int m; /* mask for bit lengths table */ + unsigned int n; /* number of lengths to get */ + unsigned int bl; /* lookup bits for tl */ + unsigned int bd; /* lookup bits for td */ + unsigned int nb; /* number of bit length codes */ + unsigned int nl; /* number of literal/length codes */ + unsigned int nd; /* number of distance codes */ + + unsigned int ll[286 + 30]; /* literal/length and distance code + lengths */ + unsigned int b_dynamic; /* bit buffer */ + unsigned int k_dynamic; /* number of bits in bit buffer */ + + /* make local bit buffer */ + b_dynamic = gunzip_bb; + k_dynamic = gunzip_bk; + + /* read in table lengths */ + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 5); + nl = 257 + ((unsigned int) b_dynamic & 0x1f); /* number of + literal/length codes + */ + + b_dynamic >>= 5; + k_dynamic -= 5; + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 5); + nd = 1 + ((unsigned int) b_dynamic & 0x1f); /* number of distance + codes */ + + b_dynamic >>= 5; + k_dynamic -= 5; + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 4); + nb = 4 + ((unsigned int) b_dynamic & 0xf); /* number of bit length + codes */ + + b_dynamic >>= 4; + k_dynamic -= 4; + if (nl > 286 || nd > 30) + return 1; /* bad lengths */ + + /* read in bit-length-code lengths */ + for (j = 0; j < nb; j++) + { + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 3); + ll[border[j]] = (unsigned int) b_dynamic & 7; + b_dynamic >>= 3; + k_dynamic -= 3; + } + for (; j < 19; j++) + { + ll[border[j]] = 0; + } + + /* build decoding table for trees--single level, 7 bit lookup */ + bl = 7; + i = huft_build(ll, 19, 19, NULL, NULL, &tl, &bl,HUFT_MMP1); + if (i != 0) + { + if (i == 1) + huft_free(tl,HUFT_MMP1); + + return i; /* incomplete code set */ + } + + /* read in literal and distance code lengths */ + n = nl + nd; + m = mask_bits[bl]; + i = l = 0; + while ((unsigned int) i < n) + { + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, (unsigned int)bl); + j = (td = tl + ((unsigned int) b_dynamic & m))->b; + b_dynamic >>= j; + k_dynamic -= j; + j = td->v.n; + if (j < 16) /* length of code in bits (0..15) */ + ll[i++] = l = j; /* save last length in l */ + else if (j == 16) /* repeat last length 3 to 6 times */ + { + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 2); + j = 3 + ((unsigned int) b_dynamic & 3); + b_dynamic >>= 2; + k_dynamic -= 2; + if ((unsigned int) i + j > n) + return 1; + + while (j--) + { + ll[i++] = l; + } + } + else if (j == 17) /* 3 to 10 zero length codes */ + { + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 3); + j = 3 + ((unsigned int) b_dynamic & 7); + b_dynamic >>= 3; + k_dynamic -= 3; + if ((unsigned int) i + j > n) + return 1; + + while (j--) + { + ll[i++] = 0; + } + l = 0; + } + else /* j == 18: 11 to 138 zero length codes */ + { + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 7); + j = 11 + ((unsigned int) b_dynamic & 0x7f); + b_dynamic >>= 7; + k_dynamic -= 7; + if ((unsigned int) i + j > n) + return 1; + + while (j--) + { + ll[i++] = 0; + } + l = 0; + } + } + + /* free decoding table for trees */ + huft_free(tl,HUFT_MMP1); + + /* restore the global bit buffer */ + gunzip_bb = b_dynamic; + gunzip_bk = k_dynamic; + + /* build the decoding tables for literal/length and distance codes */ + bl = lbits; + + if ((i = huft_build(ll, nl, 257, cplens, cplext, &tl, &bl,HUFT_MMP1)) != 0) + { + if (i == 1) + { + error_die("Incomplete literal tree"); + huft_free(tl,HUFT_MMP1); + } + + return i; /* incomplete code set */ + } + + bd = dbits; + if ((i = huft_build(ll + nl, nd, 0, cpdist, cpdext, &td, &bd,HUFT_MMP2)) != 0) + { + if (i == 1) + { + error_die("incomplete distance tree"); + huft_free(td,HUFT_MMP2); + } + + huft_free(tl,HUFT_MMP1); + return i; /* incomplete code set */ + } + + /* decompress until an end-of-block code */ + inflate_codes(tl, td, bl, bd, 1); /* Setup inflate_codes */ + + /* huft_free code moved into inflate_codes */ + + return -2; + } + default: + /* bad block type */ + error_die("bad block type"); + } + return 0; +} + +static void calculate_gunzip_crc(void) +{ + unsigned int n; + for (n = 0; n < gunzip_outbuf_count; n++) + { + gunzip_crc = crc_table[((int) gunzip_crc ^ (gunzip_window[n])) & 0xff] + ^ (gunzip_crc >> 8); + } + gunzip_bytes_out += gunzip_outbuf_count; +} + +static int inflate_get_next_window_method = -1; /* Method == -1 for stored, -2 + for codes */ +static int inflate_get_next_window_e = 0; +static int inflate_get_next_window_needAnotherBlock = 1; + +static int inflate_get_next_window(void) +{ + gunzip_outbuf_count = 0; + + while(1) + { + int ret=0; + if (inflate_get_next_window_needAnotherBlock) + { + if(inflate_get_next_window_e) + { + calculate_gunzip_crc(); + inflate_get_next_window_e = 0; + inflate_get_next_window_needAnotherBlock = 1; + return 0; + } /* Last block */ + + inflate_get_next_window_method = inflate_block(&inflate_get_next_window_e); + inflate_get_next_window_needAnotherBlock = 0; + } + + switch (inflate_get_next_window_method) + { + case -1: ret = inflate_stored(0,0,0,0); + break; + case -2: ret = inflate_codes(0,0,0,0,0); + break; + default: + error_die("inflate error"); + } + + if (ret == 1) + { + calculate_gunzip_crc(); + return 1; /* More data left */ + } + else inflate_get_next_window_needAnotherBlock = 1; /* End of that + block */ + } + /* Doesnt get here */ +} + +/* Initialise bytebuffer, be careful not to overfill the buffer */ +static void inflate_init(unsigned int bufsize) +{ + /* Set the bytebuffer size, default is same as gunzip_wsize */ + bytebuffer_max = bufsize + 8; + bytebuffer_offset = 4; + bytebuffer_size = 0; +} + +static void inflate_cleanup(void) +{ + /* free(bytebuffer); */ +} + +USE_DESKTOP(long long) static int +inflate_unzip(struct mbreader_t *in,char* outbuffer,uint32_t outbuflen) +{ + USE_DESKTOP(long long total = 0; ) + typedef void (*sig_type)(int); + + /* Allocate all global buffers (for DYN_ALLOC option) */ + gunzip_outbuf_count = 0; + gunzip_bytes_out = 0; + gunzip_src_md = in; + + /* initialize gunzip_window, bit buffer */ + gunzip_bk = 0; + gunzip_bb = 0; + + /* Create the crc table */ + gunzip_crc = ~0; + + /* Allocate space for buffer */ + while(1) + { + int ret = inflate_get_next_window(); + if((signed int)outbuflen-(signed int)gunzip_outbuf_count<0) + { + error_msg("write_error"); + #ifdef TRIM_FILE_ON_ERROR + return USE_DESKTOP(total) + 0; + #else + return -1; + #endif + } + + memcpy(outbuffer,gunzip_window,gunzip_outbuf_count); + outbuffer+=sizeof(char)*gunzip_outbuf_count; + ifl_total+=sizeof(char)*gunzip_outbuf_count; + outbuflen-=gunzip_outbuf_count; + USE_DESKTOP(total += gunzip_outbuf_count; ) + if (ret == 0) break; + } + + /* Store unused bytes in a global buffer so calling applets can access it */ + if (gunzip_bk >= 8) + { + /* Undo too much lookahead. The next read will be byte aligned so we can + discard unused bits in the last meaningful byte. */ + bytebuffer_offset--; + bytebuffer[bytebuffer_offset] = gunzip_bb & 0xff; + gunzip_bb >>= 8; + gunzip_bk -= 8; + } + + return USE_DESKTOP(total) + 0; +} + +USE_DESKTOP(long long) static int +inflate_gunzip(struct mbreader_t *in,char* outbuffer,uint32_t outbuflen) +{ + uint32_t stored_crc = 0; + unsigned int count; + USE_DESKTOP(long long total = ) inflate_unzip(in, outbuffer,outbuflen); + + USE_DESKTOP(if (total < 0) return total; + + ) + + /* top up the input buffer with the rest of the trailer */ + count = bytebuffer_size - bytebuffer_offset; + if (count < 8) + { + xread(in, &bytebuffer[bytebuffer_size], 8 - count); + bytebuffer_size += 8 - count; + } + + for (count = 0; count != 4; count++) + { + stored_crc |= (bytebuffer[bytebuffer_offset] << (count * 8)); + bytebuffer_offset++; + } + + /* Validate decompression - crc */ + if (stored_crc != (~gunzip_crc)) + { + error_msg("crc error"); + + #ifdef TRIM_FILE_ON_ERROR + return USE_DESKTOP(total) + 0; + #else + return -1; + #endif + } + + /* Validate decompression - size */ + if ((signed int)gunzip_bytes_out != + (bytebuffer[bytebuffer_offset] | (bytebuffer[bytebuffer_offset+1] << 8) | + (bytebuffer[bytebuffer_offset+2] << 16) | (bytebuffer[bytebuffer_offset+3] << 24))) + { + error_msg("incorrect length"); + return -1; + } + + return USE_DESKTOP(total) + 0; +} + +/*An allocated memory buffer at least 0x13100 (72448) bytes long*/ +uint32_t decompress(const char *inbuffer,uint32_t inbuflen,char* outbuffer,uint32_t outbuflen,uint32_t offset,char* membuf) +{ + signed char status=0; + int exitcode=0; + struct mbreader_t src_md; + ifl_total=0; + /* reset statics */ + inflate_codes_resumeCopy = 0; + inflate_get_next_window_method = -1; /* Method == -1 for stored, -2 for + codes */ + inflate_get_next_window_e = 0; + inflate_get_next_window_needAnotherBlock = 1; + /* init */ + inflate_init(0x8000-8); + /*Memory init*/ + huftbuffer1=membuf; + huftbuffer2=membuf+0x2A00; + gunzip_window=membuf+0x2A00+0xA00; + bytebuffer=membuf+0x2A00+0xA00+0x8000; + wpw_init_mempool_pdm(HUFT_MMP1,(unsigned char*)huftbuffer1,0x2A00); + wpw_init_mempool_pdm(HUFT_MMP2,(unsigned char*)huftbuffer2,0xA00); + + /* Initialize memory buffer reader */ + src_md.ptr = inbuffer; + src_md.size = inbuflen; + src_md.offset = offset; + + if ((exitcode=xread_char(&src_md)) == 0x1f) + { + unsigned char magic2; + magic2 = xread_char(&src_md); + if (magic2 == 0x8b) + { + check_header_gzip(&src_md); /* FIXME: xfunc? _or_die? */ + status = inflate_gunzip(&src_md, outbuffer,outbuflen); + } + else + { + error_msg("invalid magic"); + exitcode = -1; + } + + if (status < 0) + { + error_msg("error inflating"); + exitcode = -1; + } + } + else + { + error_msg("invalid magic"); + exitcode = -1; + } + + inflate_cleanup(); + wpw_destroy_mempool(HUFT_MMP1); + wpw_destroy_mempool(HUFT_MMP2); + + if(exitcode==-1) + return 0; + + return ifl_total; +} diff --git a/apps/codecs/libgme/inflate/inflate.h b/apps/codecs/libgme/inflate/inflate.h new file mode 100644 index 0000000000..05164621b9 --- /dev/null +++ b/apps/codecs/libgme/inflate/inflate.h @@ -0,0 +1,30 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef INFLATE_H +#define INFLATE_H +#include + +#if defined(ROCKBOX) +#include "codeclib.h" +#endif + +uint32_t decompress(const char *inbuffer,uint32_t inbuflen,char* outbuffer,uint32_t outbuflen, + uint32_t offset,char* membuf); +#endif diff --git a/apps/codecs/libgme/inflate/mallocer.c b/apps/codecs/libgme/inflate/mallocer.c new file mode 100644 index 0000000000..41abedd09f --- /dev/null +++ b/apps/codecs/libgme/inflate/mallocer.c @@ -0,0 +1,86 @@ + +/* + Based on the wiki viewer mallocer + Copyright (C) 2005 Dave Chapman + + @ Modified to decompress memory buffer by gama + */ + +#include "mallocer.h" +#include "codeclib.h" + +unsigned char* mallocbuffer[MEMPOOL_MAX]; +long memory_ptr[MEMPOOL_MAX]; +size_t buffersize[MEMPOOL_MAX]; + +int wpw_init_mempool(unsigned char mempool) +{ + memory_ptr[mempool] = 0; + mallocbuffer[mempool] = (unsigned char *)ci->codec_get_buffer(&buffersize[mempool]); + // memset(mallocbuf[mempool], 0, bufsize[mempool]); + return 0; +} + +int wpw_init_mempool_pdm(unsigned char mempool, + unsigned char* mem,long memsize) +{ + memory_ptr[mempool] = 0; + mallocbuffer[mempool] = mem; + buffersize[mempool]=memsize; + return 0; +} + +void wpw_reset_mempool(unsigned char mempool) +{ + memory_ptr[mempool]=0; +} + +void wpw_destroy_mempool(unsigned char mempool) +{ + memory_ptr[mempool] = 0; + mallocbuffer[mempool] =0; + buffersize[mempool]=0; +} + +long wpw_available(unsigned char mempool) +{ + return buffersize[mempool]-memory_ptr[mempool]; +} + +void* wpw_malloc(unsigned char mempool,size_t size) +{ + void* x; + + if (memory_ptr[mempool] + size > buffersize[mempool] ) + return NULL; + + x=&mallocbuffer[mempool][memory_ptr[mempool]]; + memory_ptr[mempool]+=(size+3)&~3; /* Keep memory 32-bit aligned */ + + return(x); +} + +void* wpw_calloc(unsigned char mempool,size_t nmemb, size_t size) +{ + void* x; + x = wpw_malloc(mempool,nmemb*size); + if (x == NULL) + return NULL; + + memset(x,0,nmemb*size); + return(x); +} + +void wpw_free(unsigned char mempool,void* ptr) +{ + (void)ptr; + (void)mempool; +} + +void* wpw_realloc(unsigned char mempool,void* ptr, size_t size) +{ + void* x; + (void)ptr; + x = wpw_malloc(mempool,size); + return(x); +} diff --git a/apps/codecs/libgme/inflate/mallocer.h b/apps/codecs/libgme/inflate/mallocer.h new file mode 100644 index 0000000000..091643443c --- /dev/null +++ b/apps/codecs/libgme/inflate/mallocer.h @@ -0,0 +1,16 @@ + +#define MEMPOOL_MAX 10 +#include +#include + +int wpw_init_mempool(unsigned char mempool); +int wpw_init_mempool_pdm(unsigned char mempool, + unsigned char* mem,long memsize); + +void wpw_reset_mempool(unsigned char mempool); +void wpw_destroy_mempool(unsigned char mempool); +void* wpw_malloc(unsigned char mempool,size_t size); +void* wpw_calloc(unsigned char mempool,size_t nmemb, size_t size); +void wpw_free(unsigned char mempool,void* ptr); +void* wpw_realloc(unsigned char mempool,void* ptr, size_t size); +long wpw_available(unsigned char mempool); diff --git a/apps/codecs/libgme/inflate/mbreader.c b/apps/codecs/libgme/inflate/mbreader.c new file mode 100644 index 0000000000..96e45cd6c8 --- /dev/null +++ b/apps/codecs/libgme/inflate/mbreader.c @@ -0,0 +1,16 @@ + +/* Memory buffer reader, simulates file read + @ gama +*/ + +#include "mbreader.h" + +int mbread(struct mbreader_t *md, void *buf, size_t n) +{ + if (!md) return -1; + size_t read_bytes = (md->offset+n) > md->size ? + md->size-md->offset : n; + memcpy(buf,md->ptr + md->offset,read_bytes); + md->offset += read_bytes; + return read_bytes; +} diff --git a/apps/codecs/libgme/inflate/mbreader.h b/apps/codecs/libgme/inflate/mbreader.h new file mode 100644 index 0000000000..6427f18231 --- /dev/null +++ b/apps/codecs/libgme/inflate/mbreader.h @@ -0,0 +1,15 @@ + +#ifndef MBREADER_H +#define MBREADER_H + +#include "codeclib.h" + +struct mbreader_t { + const char *ptr; + size_t size; + size_t offset; +}; + +int mbread(struct mbreader_t *md, void *buf, size_t n); + +#endif diff --git a/apps/codecs/libgme/kss_cpu.c b/apps/codecs/libgme/kss_cpu.c new file mode 100644 index 0000000000..891a7df255 --- /dev/null +++ b/apps/codecs/libgme/kss_cpu.c @@ -0,0 +1,35 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "kss_emu.h" + +#include "blargg_endian.h" +//#include "z80_cpu_log.h" + +/* Copyright (C) 2006-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#define OUT_PORT( addr, data ) cpu_out( this, TIME(), addr, data ) +#define IN_PORT( addr ) cpu_in( this, TIME(), addr ) +#define WRITE_MEM( addr, data ) {FLUSH_TIME(); cpu_write( this, addr, data );} +#define IDLE_ADDR idle_addr + +#define CPU_BEGIN \ +bool run_cpu( struct Kss_Emu* this, kss_time_t end_time )\ +{\ + struct Z80_Cpu *cpu = &this->cpu; \ + Z80_set_end_time( cpu, end_time ); + + #include "z80_cpu_run.h" + + return warning; +} diff --git a/apps/codecs/libgme/kss_emu.c b/apps/codecs/libgme/kss_emu.c new file mode 100644 index 0000000000..b01034234a --- /dev/null +++ b/apps/codecs/libgme/kss_emu.c @@ -0,0 +1,883 @@ +// Game_Music_Emu 0.5.5. http://www.slack.net/~ant/ + +#include "kss_emu.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +long const clock_rate = 3579545; + +const char gme_wrong_file_type [] = "Wrong file type for this emulator"; + +int const stereo = 2; // number of channels for stereo +int const silence_max = 6; // seconds +int const silence_threshold = 0x10; +long const fade_block_size = 512; +int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) + +void clear_track_vars( struct Kss_Emu* this ) +{ + this->current_track = -1; + this->out_time = 0; + this->emu_time = 0; + this->emu_track_ended_ = true; + this->track_ended = true; + this->fade_start = INT_MAX / 2 + 1; + this->fade_step = 1; + this->silence_time = 0; + this->silence_count = 0; + this->buf_remain = 0; + // warning(); // clear warning +} + +static blargg_err_t init_opl_apu( enum opl_type_t type, struct Opl_Apu* out ) +{ + blip_time_t const period = 72; + int const rate = clock_rate / period; + return Opl_init( out, rate * period, rate, period, type ); +} + +void Kss_init( struct Kss_Emu* this ) +{ + this->sample_rate = 0; + this->mute_mask_ = 0; + this->tempo = 1.0; + this->gain = 1.0; + this->chip_flags = 0; + + // defaults + this->max_initial_silence = 2; + this->silence_lookahead = 6; + this->ignore_silence = false; + + this->voice_count = 0; + clear_track_vars( this ); + + memset( this->unmapped_read, 0xFF, sizeof this->unmapped_read ); + + // Init all stuff + Buffer_init( &this->stereo_buffer ); + + Z80_init( &this->cpu ); + Rom_init( &this->rom, page_size ); + + // Initialize all apus just once (?) + Sms_apu_init( &this->sms.psg); + Ay_apu_init( &this->msx.psg ); + Scc_init( &this->msx.scc ); + +#ifndef KSS_EMU_NO_FMOPL + init_opl_apu( type_smsfmunit, &this->sms.fm ); + init_opl_apu( type_msxmusic, &this->msx.music ); + init_opl_apu( type_msxaudio, &this->msx.audio ); +#endif +} + +// Track info + +static blargg_err_t check_kss_header( void const* header ) +{ + if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) ) + return gme_wrong_file_type; + return 0; +} + +// Setup + +void update_gain( struct Kss_Emu* this ) +{ + double g = this->gain; + if ( msx_music_enabled( this ) || msx_audio_enabled( this ) + || sms_fm_enabled( this ) ) + { + g *= 0.75; + } + else + { + if ( this->scc_accessed ) + g *= 1.2; + } + + if ( sms_psg_enabled( this ) ) Sms_apu_volume( &this->sms.psg, g ); + if ( sms_fm_enabled( this ) ) Opl_volume( &this->sms.fm, g ); + if ( msx_psg_enabled( this ) ) Ay_apu_volume( &this->msx.psg, g ); + if ( msx_scc_enabled( this ) ) Scc_volume( &this->msx.scc, g ); + if ( msx_music_enabled( this ) ) Opl_volume( &this->msx.music, g ); + if ( msx_audio_enabled( this ) ) Opl_volume( &this->msx.audio, g ); +} + +blargg_err_t Kss_load_mem( struct Kss_Emu* this, const void* data, long size ) +{ + /* warning( core.warning() ); */ + memset( &this->header, 0, sizeof this->header ); + assert( offsetof (header_t,msx_audio_vol) == header_size - 1 ); + RETURN_ERR( Rom_load( &this->rom, data, size, header_base_size, &this->header, 0 ) ); + + RETURN_ERR( check_kss_header( this->header.tag ) ); + + this->chip_flags = 0; + this->header.last_track [0] = 255; + if ( this->header.tag [3] == 'C' ) + { + if ( this->header.extra_header ) + { + this->header.extra_header = 0; + /* warning( "Unknown data in header" ); */ + } + if ( this->header.device_flags & ~0x0F ) + { + this->header.device_flags &= 0x0F; + /* warning( "Unknown data in header" ); */ + } + } + else if ( this->header.extra_header ) + { + if ( this->header.extra_header != header_ext_size ) + { + this->header.extra_header = 0; + /* warning( "Invalid extra_header_size" ); */ + } + else + { + memcpy( this->header.data_size, this->rom.file_data, header_ext_size ); + } + } + + #ifndef NDEBUG + { + int ram_mode = this->header.device_flags & 0x84; // MSX + if ( this->header.device_flags & 0x02 ) // SMS + ram_mode = (this->header.device_flags & 0x88); + + if ( ram_mode ) + blargg_dprintf_( "RAM not supported\n" ); // TODO: support + } + #endif + + this->track_count = get_le16( this->header.last_track ) + 1; + this->m3u.size = 0; + + this->scc_enabled = false; + if ( this->header.device_flags & 0x02 ) // Sega Master System + { + int const osc_count = sms_osc_count + opl_osc_count; + + // sms.psg + this->voice_count = sms_osc_count; + this->chip_flags |= sms_psg_flag; + + // sms.fm + if ( this->header.device_flags & 0x01 ) + { + this->voice_count = osc_count; + this->chip_flags |= sms_fm_flag; + } + } + else // MSX + { + int const osc_count = ay_osc_count + opl_osc_count; + + // msx.psg + this->voice_count = ay_osc_count; + this->chip_flags |= msx_psg_flag; + + /* if ( this->header.device_flags & 0x10 ) + warning( "MSX stereo not supported" ); */ + + // msx.music + if ( this->header.device_flags & 0x01 ) + { + this->voice_count = osc_count; + this->chip_flags |= msx_music_flag; + } + + #ifndef KSS_EMU_NO_FMOPL + // msx.audio + if ( this->header.device_flags & 0x08 ) + { + this->voice_count = osc_count; + this->chip_flags |= msx_audio_flag; + } + #endif + + if ( !(this->header.device_flags & 0x80) ) + { + if ( !(this->header.device_flags & 0x84) ) + this->scc_enabled = scc_enabled_true; + + // msx.scc + this->chip_flags |= msx_scc_flag; + this->voice_count = ay_osc_count + scc_osc_count; + } + } + + this->silence_lookahead = 6; + if ( sms_fm_enabled( this ) || msx_music_enabled( this ) || msx_audio_enabled( this ) ) + { + if ( !Opl_supported() ) + ; /* warning( "FM sound not supported" ); */ + else + this->silence_lookahead = 3; // Opl_Apu is really slow + } + + this->clock_rate_ = clock_rate; + Buffer_clock_rate( &this->stereo_buffer, clock_rate ); + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buffer ); + + Sound_set_tempo( this, this->tempo ); + Sound_mute_voices( this, this->mute_mask_ ); + return 0; +} + +void set_voice( struct Kss_Emu* this, int i, struct Blip_Buffer* center, struct Blip_Buffer* left, struct Blip_Buffer* right ) +{ + if ( sms_psg_enabled( this ) ) // Sega Master System + { + i -= sms_osc_count; + if ( i < 0 ) + { + Sms_apu_set_output( &this->sms.psg, i + sms_osc_count, center, left, right ); + return; + } + + if ( sms_fm_enabled( this ) && i < opl_osc_count ) + Opl_set_output( &this->sms.fm, center ); + } + else if ( msx_psg_enabled( this ) ) // MSX + { + i -= ay_osc_count; + if ( i < 0 ) + { + Ay_apu_set_output( &this->msx.psg, i + ay_osc_count, center ); + return; + } + + if ( msx_scc_enabled( this ) && i < scc_osc_count ) Scc_set_output( &this->msx.scc, i, center ); + if ( msx_music_enabled( this ) && i < opl_osc_count ) Opl_set_output( &this->msx.music, center ); + if ( msx_audio_enabled( this ) && i < opl_osc_count ) Opl_set_output( &this->msx.audio, center ); + } +} + +// Emulation + +void jsr( struct Kss_Emu* this, byte const addr [] ) +{ + this->ram [--this->cpu.r.sp] = idle_addr >> 8; + this->ram [--this->cpu.r.sp] = idle_addr & 0xFF; + this->cpu.r.pc = get_le16( addr ); +} + +void set_bank( struct Kss_Emu* this, int logical, int physical ) +{ + int const bank_size = (16 * 1024L) >> (this->header.bank_mode >> 7 & 1); + + int addr = 0x8000; + if ( logical && bank_size == 8 * 1024 ) + addr = 0xA000; + + physical -= this->header.first_bank; + if ( (unsigned) physical >= (unsigned) this->bank_count ) + { + byte* data = this->ram + addr; + Z80_map_mem( &this->cpu, addr, bank_size, data, data ); + } + else + { + int offset, phys = physical * bank_size; + for ( offset = 0; offset < bank_size; offset += page_size ) + Z80_map_mem( &this->cpu, addr + offset, page_size, + this->unmapped_write, Rom_at_addr( &this->rom, phys + offset ) ); + + } +} + +void cpu_write( struct Kss_Emu* this, addr_t addr, int data ) +{ + *Z80_write( &this->cpu, addr ) = data; + if ( (addr & this->scc_enabled) == 0x8000 ) { + // TODO: SCC+ support + + data &= 0xFF; + switch ( addr ) + { + case 0x9000: + set_bank( this, 0, data ); + return; + + case 0xB000: + set_bank( this, 1, data ); + return; + + case 0xBFFE: // selects between mapping areas (we just always enable both) + if ( data == 0 || data == 0x20 ) + return; + } + + int scc_addr = (addr & 0xDFFF) - 0x9800; + if ( msx_scc_enabled( this ) && (unsigned) scc_addr < 0xB0 ) + { + this->scc_accessed = true; + //if ( (unsigned) (scc_addr - 0x90) < 0x10 ) + // scc_addr -= 0x10; // 0x90-0x9F mirrors to 0x80-0x8F + if ( scc_addr < scc_reg_count ) + Scc_write( &this->msx.scc, Z80_time( &this->cpu ), addr, data ); + return; + } + } +} + +void cpu_out( struct Kss_Emu* this, kss_time_t time, kss_addr_t addr, int data ) +{ + data &= 0xFF; + switch ( addr & 0xFF ) + { + case 0xA0: + if ( msx_psg_enabled( this ) ) + Ay_apu_write_addr( &this->msx.psg, data ); + return; + + case 0xA1: + if ( msx_psg_enabled( this ) ) + Ay_apu_write_data( &this->msx.psg, time, data ); + return; + + case 0x06: + if ( sms_psg_enabled( this ) && (this->header.device_flags & 0x04) ) + { + Sms_apu_write_ggstereo( &this->sms.psg, time, data ); + return; + } + break; + + case 0x7E: + case 0x7F: + if ( sms_psg_enabled( this ) ) + { + Sms_apu_write_data( &this->sms.psg, time, data ); + return; + } + break; + + #define OPL_WRITE_HANDLER( base, name, opl )\ + case base : if ( name##_enabled( this ) ) { Opl_write_addr( opl, data ); return; } break;\ + case base+1: if ( name##_enabled( this ) ) { Opl_write_data( opl, time, data ); return; } break; + + OPL_WRITE_HANDLER( 0x7C, msx_music, &this->msx.music ) + OPL_WRITE_HANDLER( 0xC0, msx_audio, &this->msx.audio ) + OPL_WRITE_HANDLER( 0xF0, sms_fm, &this->sms.fm ) + + case 0xFE: + set_bank( this, 0, data ); + return; + + #ifndef NDEBUG + case 0xA8: // PPI + return; + #endif + } + + /* cpu_out( time, addr, data ); */ +} + +int cpu_in( struct Kss_Emu* this, kss_time_t time, kss_addr_t addr ) +{ + switch ( addr & 0xFF ) + { + case 0xC0: + case 0xC1: + if ( msx_audio_enabled( this ) ) + return Opl_read( &this->msx.audio, time, addr & 1 ); + break; + + case 0xA2: + if ( msx_psg_enabled( this ) ) + return Ay_apu_read( &this->msx.psg ); + break; + + #ifndef NDEBUG + case 0xA8: // PPI + return 0; + #endif + } + + /* return cpu_in( time, addr ); */ + return 0xFF; +} + +blargg_err_t run_clocks( struct Kss_Emu* this, blip_time_t* duration_ ) +{ + blip_time_t duration = *duration_; + RETURN_ERR( end_frame( this, duration ) ); + + if ( sms_psg_enabled( this ) ) Sms_apu_end_frame( &this->sms.psg, duration ); + if ( sms_fm_enabled( this ) ) Opl_end_frame( &this->sms.fm, duration ); + if ( msx_psg_enabled( this ) ) Ay_apu_end_frame( &this->msx.psg, duration ); + if ( msx_scc_enabled( this ) ) Scc_end_frame( &this->msx.scc, duration ); + if ( msx_music_enabled( this ) ) Opl_end_frame( &this->msx.music, duration ); + if ( msx_audio_enabled( this ) ) Opl_end_frame( &this->msx.audio, duration ); + + return 0; +} + +blargg_err_t end_frame( struct Kss_Emu* this, kss_time_t end ) +{ + while ( Z80_time( &this->cpu ) < end ) + { + kss_time_t next = min( end, this->next_play ); + run_cpu( this, next ); + if ( this->cpu.r.pc == idle_addr ) + Z80_set_time( &this->cpu, next ); + + if ( Z80_time( &this->cpu ) >= this->next_play ) + { + this->next_play += this->play_period; + if ( this->cpu.r.pc == idle_addr ) + { + if ( !this->gain_updated ) + { + this->gain_updated = true; + update_gain( this ); + } + + jsr( this, this->header.play_addr ); + } + } + } + + this->next_play -= end; + check( this->next_play >= 0 ); + Z80_adjust_time( &this->cpu, -end ); + + return 0; +} + +// MUSIC + + +blargg_err_t Kss_set_sample_rate( struct Kss_Emu* this, long rate ) +{ + require( !this->sample_rate ); // sample rate can't be changed once set + RETURN_ERR( Buffer_set_sample_rate( &this->stereo_buffer, rate, 1000 / 20 ) ); + + // Set bass frequency + Buffer_bass_freq( &this->stereo_buffer, 180 ); + this->sample_rate = rate; + return 0; +} + +void Sound_mute_voice( struct Kss_Emu* this, int index, bool mute ) +{ + require( (unsigned) index < (unsigned) this->voice_count ); + int bit = 1 << index; + int mask = this->mute_mask_ | bit; + if ( !mute ) + mask ^= bit; + Sound_mute_voices( this, mask ); +} + +void Sound_mute_voices( struct Kss_Emu* this, int mask ) +{ + require( this->sample_rate ); // sample rate must be set first + this->mute_mask_ = mask; + + int i; + for ( i = this->voice_count; i--; ) + { + if ( mask & (1 << i) ) + { + set_voice( this, i, 0, 0, 0 ); + } + else + { + struct channel_t ch = Buffer_channel( &this->stereo_buffer ); + assert( (ch.center && ch.left && ch.right) || + (!ch.center && !ch.left && !ch.right) ); // all or nothing + set_voice( this, i, ch.center, ch.left, ch.right ); + } + } +} + +void Sound_set_tempo( struct Kss_Emu* this, double t ) +{ + require( this->sample_rate ); // sample rate must be set first + double const min = 0.02; + double const max = 4.00; + if ( t < min ) t = min; + if ( t > max ) t = max; + this->tempo = t; + + blip_time_t period = + (this->header.device_flags & 0x40 ? clock_rate / 50 : clock_rate / 60); + this->play_period = (blip_time_t) (period / t); +} + +void fill_buf( struct Kss_Emu* this ); +blargg_err_t Kss_start_track( struct Kss_Emu* this, int track ) +{ + clear_track_vars( this ); + + // Remap track if playlist available + if ( this->m3u.size > 0 ) { + struct entry_t* e = &this->m3u.entries[track]; + track = e->track; + } + + this->current_track = track; + + Buffer_clear( &this->stereo_buffer ); + + if ( sms_psg_enabled( this ) ) Sms_apu_reset( &this->sms.psg, 0, 0 ); + if ( sms_fm_enabled( this ) ) Opl_reset( &this->sms.fm ); + if ( msx_psg_enabled( this ) ) Ay_apu_reset( &this->msx.psg ); + if ( msx_scc_enabled( this ) ) Scc_reset( &this->msx.scc ); + if ( msx_music_enabled( this ) ) Opl_reset( &this->msx.music ); + if ( msx_audio_enabled( this ) ) Opl_reset( &this->msx.audio ); + + this->scc_accessed = false; + update_gain( this ); + + memset( this->ram, 0xC9, 0x4000 ); + memset( this->ram + 0x4000, 0, sizeof this->ram - 0x4000 ); + + // copy driver code to lo RAM + static byte const bios [] = { + 0xD3, 0xA0, 0xF5, 0x7B, 0xD3, 0xA1, 0xF1, 0xC9, // $0001: WRTPSG + 0xD3, 0xA0, 0xDB, 0xA2, 0xC9 // $0009: RDPSG + }; + static byte const vectors [] = { + 0xC3, 0x01, 0x00, // $0093: WRTPSG vector + 0xC3, 0x09, 0x00, // $0096: RDPSG vector + }; + memcpy( this->ram + 0x01, bios, sizeof bios ); + memcpy( this->ram + 0x93, vectors, sizeof vectors ); + + // copy non-banked data into RAM + int load_addr = get_le16( this->header.load_addr ); + int orig_load_size = get_le16( this->header.load_size ); + int load_size = min( orig_load_size, (int) this->rom.file_size ); + load_size = min( load_size, (int) mem_size - load_addr ); + /* if ( load_size != orig_load_size ) + warning( "Excessive data size" ); */ + memcpy( this->ram + load_addr, this->rom.file_data + this->header.extra_header, load_size ); + + Rom_set_addr( &this->rom, -load_size - this->header.extra_header ); + + // check available bank data + int const bank_size = (16 * 1024L) >> (this->header.bank_mode >> 7 & 1); + int max_banks = (this->rom.file_size - load_size + bank_size - 1) / bank_size; + this->bank_count = this->header.bank_mode & 0x7F; + if ( this->bank_count > max_banks ) + { + this->bank_count = max_banks; + /* warning( "Bank data missing" ); */ + } + //dprintf( "load_size : $%X\n", load_size ); + //dprintf( "bank_size : $%X\n", bank_size ); + //dprintf( "bank_count: %d (%d claimed)\n", bank_count, this->header.bank_mode & 0x7F ); + + this->ram [idle_addr] = 0xFF; + Z80_reset( &this->cpu, this->unmapped_write, this->unmapped_read ); + Z80_map_mem( &this->cpu, 0, mem_size, this->ram, this->ram ); + + this->cpu.r.sp = 0xF380; + this->cpu.r.b.a = track; + this->cpu.r.b.h = 0; + this->next_play = this->play_period; + this->gain_updated = false; + jsr( this, this->header.init_addr ); + + this->emu_track_ended_ = false; + this->track_ended = false; + + if ( !this->ignore_silence ) + { + // play until non-silence or end of track + long end; + for ( end = this->max_initial_silence * stereo * this->sample_rate; this->emu_time < end; ) + { + fill_buf( this ); + if ( this->buf_remain | (int) this->emu_track_ended_ ) + break; + } + + this->emu_time = this->buf_remain; + this->out_time = 0; + this->silence_time = 0; + this->silence_count = 0; + } + /* return track_ended() ? warning() : 0; */ + return 0; +} + +// Tell/Seek + +blargg_long msec_to_samples( blargg_long msec, long sample_rate ) +{ + blargg_long sec = msec / 1000; + msec -= sec * 1000; + return (sec * sample_rate + msec * sample_rate / 1000) * stereo; +} + +long Track_tell( struct Kss_Emu* this ) +{ + blargg_long rate = this->sample_rate * stereo; + blargg_long sec = this->out_time / rate; + return sec * 1000 + (this->out_time - sec * rate) * 1000 / rate; +} + +blargg_err_t Track_seek( struct Kss_Emu* this, long msec ) +{ + blargg_long time = msec_to_samples( msec, this->sample_rate ); + if ( time < this->out_time ) + RETURN_ERR( Kss_start_track( this, this->current_track ) ); + return Track_skip( this, time - this->out_time ); +} + +blargg_err_t play_( struct Kss_Emu* this, long count, sample_t* out ); +blargg_err_t skip_( struct Kss_Emu* this, long count ) +{ + // for long skip, mute sound + const long threshold = 30000; + if ( count > threshold ) + { + int saved_mute = this->mute_mask_; + Sound_mute_voices( this, ~0 ); + + while ( count > threshold / 2 && !this->emu_track_ended_ ) + { + RETURN_ERR( play_( this, buf_size, this->buf ) ); + count -= buf_size; + } + + Sound_mute_voices( this, saved_mute ); + } + + while ( count && !this->emu_track_ended_ ) + { + long n = buf_size; + if ( n > count ) + n = count; + count -= n; + RETURN_ERR( play_( this, n, this->buf ) ); + } + return 0; +} + +blargg_err_t Track_skip( struct Kss_Emu* this, long count ) +{ + require( this->current_track >= 0 ); // start_track() must have been called already + this->out_time += count; + + // remove from silence and buf first + { + long n = min( count, this->silence_count ); + this->silence_count -= n; + count -= n; + + n = min( count, this->buf_remain ); + this->buf_remain -= n; + count -= n; + } + + if ( count && !this->emu_track_ended_ ) + { + this->emu_time += count; + if ( skip_( this, count ) ) + this->emu_track_ended_ = true; + } + + if ( !(this->silence_count | this->buf_remain) ) // caught up to emulator, so update track ended + this->track_ended |= this->emu_track_ended_; + + return 0; +} + +// Fading + +void Track_set_fade( struct Kss_Emu* this, long start_msec, long length_msec ) +{ + this->fade_step = this->sample_rate * length_msec / (fade_block_size * fade_shift * 1000 / stereo); + this->fade_start = msec_to_samples( start_msec, this->sample_rate ); +} + +// unit / pow( 2.0, (double) x / step ) +static int int_log( blargg_long x, int step, int unit ) +{ + int shift = x / step; + int fraction = (x - shift * step) * unit / step; + return ((unit - fraction) + (fraction >> 1)) >> shift; +} + +void handle_fade( struct Kss_Emu *this, long out_count, sample_t* out ) +{ + int i; + for ( i = 0; i < out_count; i += fade_block_size ) + { + int const shift = 14; + int const unit = 1 << shift; + int gain = int_log( (this->out_time + i - this->fade_start) / fade_block_size, + this->fade_step, unit ); + if ( gain < (unit >> fade_shift) ) + this->track_ended = this->emu_track_ended_ = true; + + sample_t* io = &out [i]; + int count; + for ( count = min( fade_block_size, out_count - i ); count; --count ) + { + *io = (sample_t) ((*io * gain) >> shift); + ++io; + } + } +} + +// Silence detection + +void emu_play( struct Kss_Emu* this, long count, sample_t* out ) +{ + check( current_track_ >= 0 ); + this->emu_time += count; + if ( this->current_track >= 0 && !this->emu_track_ended_ ) { + if ( play_( this, count, out ) ) + this->emu_track_ended_ = true; + } + else + memset( out, 0, count * sizeof *out ); +} + +// number of consecutive silent samples at end +static long count_silence( sample_t* begin, long size ) +{ + sample_t first = *begin; + *begin = silence_threshold; // sentinel + sample_t* p = begin + size; + while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { } + *begin = first; + return size - (p - begin); +} + +// fill internal buffer and check it for silence +void fill_buf( struct Kss_Emu* this ) +{ + assert( !this->buf_remain ); + if ( !this->emu_track_ended_ ) + { + emu_play( this, buf_size, this->buf ); + long silence = count_silence( this->buf, buf_size ); + if ( silence < buf_size ) + { + this->silence_time = this->emu_time - silence; + this->buf_remain = buf_size; + return; + } + } + this->silence_count += buf_size; +} + +blargg_err_t Kss_play( struct Kss_Emu* this, long out_count, sample_t* out ) +{ + if ( this->track_ended ) + { + memset( out, 0, out_count * sizeof *out ); + } + else + { + require( this->current_track >= 0 ); + require( out_count % stereo == 0 ); + + assert( this->emu_time >= this->out_time ); + + // prints nifty graph of how far ahead we are when searching for silence + //debug_printf( "%*s \n", int ((emu_time - out_time) * 7 / sample_rate()), "*" ); + + long pos = 0; + if ( this->silence_count ) + { + // during a run of silence, run emulator at >=2x speed so it gets ahead + long ahead_time = this->silence_lookahead * (this->out_time + out_count -this->silence_time) + this->silence_time; + while ( this->emu_time < ahead_time && !(this->buf_remain |this-> emu_track_ended_) ) + fill_buf( this ); + + // fill with silence + pos = min( this->silence_count, out_count ); + memset( out, 0, pos * sizeof *out ); + this->silence_count -= pos; + + if ( this->emu_time - this->silence_time > silence_max * stereo * this->sample_rate ) + { + this->track_ended = this->emu_track_ended_ = true; + this->silence_count = 0; + this->buf_remain = 0; + } + } + + if ( this->buf_remain ) + { + // empty silence buf + long n = min( this->buf_remain, out_count - pos ); + memcpy( &out [pos], this->buf + (buf_size - this->buf_remain), n * sizeof *out ); + this->buf_remain -= n; + pos += n; + } + + // generate remaining samples normally + long remain = out_count - pos; + if ( remain ) + { + emu_play( this, remain, out + pos ); + this->track_ended |= this->emu_track_ended_; + + if ( !this->ignore_silence || this->out_time > this->fade_start ) + { + // check end for a new run of silence + long silence = count_silence( out + pos, remain ); + if ( silence < remain ) + this->silence_time = this->emu_time - silence; + + if ( this->emu_time - this->silence_time >= buf_size ) + fill_buf( this ); // cause silence detection on next play() + } + } + + if ( this->out_time > this->fade_start ) + handle_fade( this, out_count, out ); + } + this->out_time += out_count; + return 0; +} + +blargg_err_t play_( struct Kss_Emu* this, long count, sample_t* out ) +{ + long remain = count; + while ( remain ) + { + remain -= Buffer_read_samples( &this->stereo_buffer, &out [count - remain], remain ); + if ( remain ) + { + if ( this->buf_changed_count != Buffer_channels_changed_count( &this->stereo_buffer ) ) + { + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buffer ); + Sound_mute_voices( this, this->mute_mask_ ); + } + int msec = Buffer_length( &this->stereo_buffer ); + /* blip_time_t clocks_emulated = (blargg_long) msec * clock_rate_ / 1000; */ + blip_time_t clocks_emulated = msec * this->clock_rate_ / 1000 - 100; + RETURN_ERR( run_clocks( this, &clocks_emulated ) ); + assert( clocks_emulated ); + Buffer_end_frame( &this->stereo_buffer, clocks_emulated ); + } + } + return 0; +} + diff --git a/apps/codecs/libgme/kss_emu.h b/apps/codecs/libgme/kss_emu.h new file mode 100644 index 0000000000..81db2ae708 --- /dev/null +++ b/apps/codecs/libgme/kss_emu.h @@ -0,0 +1,228 @@ +// MSX computer KSS music file emulator + +// Game_Music_Emu 0.5.5 +#ifndef KSS_EMU_H +#define KSS_EMU_H + +#include "gme.h" +#include "blargg_common.h" + +#include "rom_data.h" +#include "multi_buffer.h" + +#include "kss_scc_apu.h" +#include "z80_cpu.h" +#include "sms_apu.h" +#include "ay_apu.h" +#include "opl_apu.h" +#include "m3u_playlist.h" + +typedef short sample_t; +typedef int kss_time_t; +typedef int kss_addr_t; +typedef struct Z80_Cpu Kss_Cpu; + +// Sound chip flags +enum { + sms_psg_flag = 1 << 0, + sms_fm_flag = 1 << 1, + msx_psg_flag = 1 << 2, + msx_scc_flag = 1 << 3, + msx_music_flag = 1 << 4, + msx_audio_flag = 1 << 5 +}; + +enum { idle_addr = 0xFFFF }; +enum { scc_enabled_true = 0xC000 }; +enum { mem_size = 0x10000 }; +enum { buf_size = 2048 }; + +// KSS file header +enum { header_size = 0x20 }; +enum { header_base_size = 0x10 }; +enum { header_ext_size = header_size - header_base_size }; + +struct header_t +{ + byte tag [4]; + byte load_addr [2]; + byte load_size [2]; + byte init_addr [2]; + byte play_addr [2]; + byte first_bank; + byte bank_mode; + byte extra_header; + byte device_flags; + + // KSSX extended data, if extra_header==0x10 + byte data_size [4]; + byte unused [4]; + byte first_track [2]; + byte last_track [2]; // if no extended data, we set this to 0xFF + byte psg_vol; + byte scc_vol; + byte msx_music_vol; + byte msx_audio_vol; +}; + +struct sms_t { + struct Sms_Apu psg; + struct Opl_Apu fm; +}; + +struct msx_t { + struct Ay_Apu psg; + struct Scc_Apu scc; + struct Opl_Apu music; + struct Opl_Apu audio; +}; + +struct Kss_Emu { + struct header_t header; + + int chip_flags; + bool scc_accessed; + bool gain_updated; + + int track_count; + + unsigned scc_enabled; // 0 or 0xC000 + int bank_count; + + blip_time_t play_period; + blip_time_t next_play; + int ay_latch; + + // general + int max_initial_silence; + int voice_count; + int mute_mask_; + double tempo; + double gain; + + long sample_rate; + + // track-specific + int current_track; + blargg_long out_time; // number of samples played since start of track + blargg_long emu_time; // number of samples emulator has generated since start of track + bool emu_track_ended_; // emulator has reached end of track + volatile bool track_ended; + + // fading + blargg_long fade_start; + int fade_step; + + // silence detection + int silence_lookahead; // speed to run emulator when looking ahead for silence + bool ignore_silence; + long silence_time; // number of samples where most recent silence began + long silence_count; // number of samples of silence to play before using buf + long buf_remain; // number of samples left in silence buffer + + struct Stereo_Buffer stereo_buffer; // NULL if using custom buffer + long clock_rate_; + unsigned buf_changed_count; + + // M3u Playlist + struct M3u_Playlist m3u; + + // large items + sample_t buf [buf_size]; + + struct sms_t sms; + struct msx_t msx; + + Kss_Cpu cpu; + struct Rom_Data rom; + + byte unmapped_read [0x100]; + byte unmapped_write [page_size]; + byte ram [mem_size + cpu_padding]; +}; + +// Basic functionality (see Gme_File.h for file loading/track info functions) + +void Kss_init( struct Kss_Emu* this ); +blargg_err_t Kss_load_mem( struct Kss_Emu* this, const void* data, long size ); +blargg_err_t end_frame( struct Kss_Emu* this, kss_time_t ); + +// Set output sample rate. Must be called only once before loading file. +blargg_err_t Kss_set_sample_rate( struct Kss_Emu* this, long sample_rate ); + +// Start a track, where 0 is the first track. Also clears warning string. +blargg_err_t Kss_start_track( struct Kss_Emu* this, int track ); + +// Generate 'count' samples info 'buf'. Output is in stereo. Any emulation +// errors set warning string, and major errors also end track. +blargg_err_t Kss_play( struct Kss_Emu* this, long count, sample_t* buf ) ICODE_ATTR; + +// Track status/control + +// Number of milliseconds (1000 msec = 1 second) played since beginning of track +long Track_tell( struct Kss_Emu* this ); + +// Seek to new time in track. Seeking backwards or far forward can take a while. +blargg_err_t Track_seek( struct Kss_Emu* this, long msec ); + +// Skip n samples +blargg_err_t Track_skip( struct Kss_Emu* this, long n ); + +// Set start time and length of track fade out. Once fade ends track_ended() returns +// true. Fade time can be changed while track is playing. +void Track_set_fade( struct Kss_Emu* this, long start_msec, long length_msec ); + +// Get track length in milliseconds +static inline long Track_get_length( struct Kss_Emu* this, int n ) +{ + long length = 0; + + if ( (this->m3u.size > 0) && (n < this->m3u.size) ) { + struct entry_t* entry = &this->m3u.entries [n]; + length = entry->length; + } + + if ( length <= 0 ) + length = 120 * 1000; /* 2 minutes */ + + return length; +} + +// Sound customization + +// Adjust song tempo, where 1.0 = normal, 0.5 = half speed, 2.0 = double speed. +// Track length as returned by track_info() assumes a tempo of 1.0. +void Sound_set_tempo( struct Kss_Emu* this, double t ); + +// Mute/unmute voice i, where voice 0 is first voice +void Sound_mute_voice( struct Kss_Emu* this, int index, bool mute ); + +// Set muting state of all voices at once using a bit mask, where -1 mutes them all, +// 0 unmutes them all, 0x01 mutes just the first voice, etc. +void Sound_mute_voices( struct Kss_Emu* this, int mask ); + +// Change overall output amplitude, where 1.0 results in minimal clamping. +// Must be called before set_sample_rate(). +static inline void Sound_set_gain( struct Kss_Emu* this, double g ) +{ + assert( !this->sample_rate ); // you must set gain before setting sample rate + this->gain = g; +} + +// Emulation (You shouldn't touch these +void cpu_write( struct Kss_Emu* this, kss_addr_t, int ) ICODE_ATTR; +int cpu_in( struct Kss_Emu* this, kss_time_t, kss_addr_t ) ICODE_ATTR; +void cpu_out( struct Kss_Emu* this, kss_time_t, kss_addr_t, int ) ICODE_ATTR; + +void cpu_write_( struct Kss_Emu* this, kss_addr_t addr, int data ) ICODE_ATTR; +bool run_cpu( struct Kss_Emu* this, kss_time_t end ) ICODE_ATTR; +void jsr( struct Kss_Emu* this, byte const addr [] ) ICODE_ATTR; + +static inline int sms_psg_enabled( struct Kss_Emu* this ) { return this->chip_flags & sms_psg_flag; } +static inline int sms_fm_enabled( struct Kss_Emu* this ) { return this->chip_flags & sms_fm_flag; } +static inline int msx_psg_enabled( struct Kss_Emu* this ) { return this->chip_flags & msx_psg_flag; } +static inline int msx_scc_enabled( struct Kss_Emu* this ) { return this->chip_flags & msx_scc_flag; } +static inline int msx_music_enabled( struct Kss_Emu* this ) { return this->chip_flags & msx_music_flag;} +static inline int msx_audio_enabled( struct Kss_Emu* this ) { return this->chip_flags & msx_audio_flag;} + +#endif diff --git a/apps/codecs/libgme/kss_scc_apu.c b/apps/codecs/libgme/kss_scc_apu.c new file mode 100644 index 0000000000..0e71b1ce3b --- /dev/null +++ b/apps/codecs/libgme/kss_scc_apu.c @@ -0,0 +1,166 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "kss_scc_apu.h" + +/* Copyright (C) 2006-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// Tones above this frequency are treated as disabled tone at half volume. +// Power of two is more efficient (avoids division). +extern int const inaudible_freq; + +int const wave_size = 0x20; + +static void set_output( struct Scc_Apu* this, struct Blip_Buffer* buf ) +{ + int i; + for ( i = 0; i < scc_osc_count; ++i ) + Scc_set_output( this, i, buf ); +} + +void Scc_volume( struct Scc_Apu* this, double v ) +{ + Synth_volume( &this->synth, 0.43 / scc_osc_count / scc_amp_range * v ); +} + +void Scc_reset( struct Scc_Apu* this ) +{ + this->last_time = 0; + + int i; + for ( i = scc_osc_count; --i >= 0; ) + memset( &this->oscs [i], 0, offsetof (struct scc_osc_t,output) ); + + memset( this->regs, 0, sizeof this->regs ); +} + +void Scc_init( struct Scc_Apu* this ) +{ + Synth_init( &this->synth); + + set_output( this, NULL ); + Scc_volume( this, 1.0 ); + Scc_reset( this ); +} + +static void run_until( struct Scc_Apu* this, blip_time_t end_time ) +{ + int index; + for ( index = 0; index < scc_osc_count; index++ ) + { + struct scc_osc_t* osc = &this->oscs [index]; + + struct Blip_Buffer* const output = osc->output; + if ( !output ) + continue; + + blip_time_t period = (this->regs [0xA0 + index * 2 + 1] & 0x0F) * 0x100 + + this->regs [0xA0 + index * 2] + 1; + int volume = 0; + if ( this->regs [0xAF] & (1 << index) ) + { + blip_time_t inaudible_period = (unsigned) (Blip_clock_rate( output ) + + inaudible_freq * 32) / (unsigned) (inaudible_freq * 16); + if ( period > inaudible_period ) + volume = (this->regs [0xAA + index] & 0x0F) * (scc_amp_range / 256 / 15); + } + + int8_t const* wave = (int8_t*) this->regs + index * wave_size; + /*if ( index == osc_count - 1 ) + wave -= wave_size; // last two oscs share same wave RAM*/ + + { + int delta = wave [osc->phase] * volume - osc->last_amp; + if ( delta ) + { + osc->last_amp += delta; + Blip_set_modified( output ); + Synth_offset( &this->synth, this->last_time, delta, output ); + } + } + + blip_time_t time = this->last_time + osc->delay; + if ( time < end_time ) + { + int phase = osc->phase; + if ( !volume ) + { + // maintain phase + int count = (end_time - time + period - 1) / period; + phase += count; // will be masked below + time += count * period; + } + else + { + int last_wave = wave [phase]; + phase = (phase + 1) & (wave_size - 1); // pre-advance for optimal inner loop + do + { + int delta = wave [phase] - last_wave; + phase = (phase + 1) & (wave_size - 1); + if ( delta ) + { + last_wave += delta; + Synth_offset_inline( &this->synth, time, delta * volume, output ); + } + time += period; + } + while ( time < end_time ); + + osc->last_amp = last_wave * volume; + Blip_set_modified( output ); + phase--; // undo pre-advance + } + osc->phase = phase & (wave_size - 1); + } + osc->delay = time - end_time; + } + this->last_time = end_time; +} + +void Scc_write( struct Scc_Apu* this, blip_time_t time, int addr, int data ) +{ + //assert( (unsigned) addr < reg_count ); + assert( ( addr >= 0x9800 && addr <= 0x988F ) || ( addr >= 0xB800 && addr <= 0xB8AF ) ); + run_until( this, time ); + + addr -= 0x9800; + if ( ( unsigned ) addr < 0x90 ) + { + if ( ( unsigned ) addr < 0x60 ) + this->regs [addr] = data; + else if ( ( unsigned ) addr < 0x80 ) + { + this->regs [addr] = this->regs[addr + 0x20] = data; + } + else if ( ( unsigned ) addr < 0x90 ) + { + this->regs [addr + 0x20] = data; + } + } + else + { + addr -= 0xB800 - 0x9800; + if ( ( unsigned ) addr < 0xB0 ) + this->regs [addr] = data; + } +} + +void Scc_end_frame( struct Scc_Apu* this, blip_time_t end_time ) +{ + if ( end_time > this->last_time ) + run_until( this, end_time ); + + this->last_time -= end_time; + assert( this->last_time >= 0 ); +} diff --git a/apps/codecs/libgme/kss_scc_apu.h b/apps/codecs/libgme/kss_scc_apu.h new file mode 100644 index 0000000000..f31228a39d --- /dev/null +++ b/apps/codecs/libgme/kss_scc_apu.h @@ -0,0 +1,51 @@ +// Konami SCC sound chip emulator + +// Game_Music_Emu 0.6-pre +#ifndef KSS_SCC_APU_H +#define KSS_SCC_APU_H + +#include "blargg_common.h" +#include "blip_buffer.h" + +enum { scc_reg_count = 0xB0 }; // 0 <= reg < reg_count +enum { scc_osc_count = 5 }; +enum { scc_amp_range = 0x8000 }; + +struct scc_osc_t +{ + int delay; + int phase; + int last_amp; + struct Blip_Buffer* output; +}; + +struct Scc_Apu { + struct scc_osc_t oscs [scc_osc_count]; + blip_time_t last_time; + unsigned char regs [scc_reg_count]; + + struct Blip_Synth synth; +}; + +void Scc_init( struct Scc_Apu* this ); + +// Resets sound chip +void Scc_reset( struct Scc_Apu* this ); + +// Set overall volume, where 1.0 is normal +void Scc_volume( struct Scc_Apu* this, double v ); + +static inline void Scc_set_output( struct Scc_Apu* this, int index, struct Blip_Buffer* b ) +{ + assert( (unsigned) index < scc_osc_count ); + this->oscs [index].output = b; +} + +// Emulates to time t, then writes data to reg +void Scc_write( struct Scc_Apu* this, blip_time_t time, int addr, int data ) ICODE_ATTR; + +// Emulates to time t, then subtracts t from the current time. +// OK if previous write call had time slightly after t. +void Scc_end_frame( struct Scc_Apu* this, blip_time_t end_time ) ICODE_ATTR; + +#endif diff --git a/apps/codecs/libgme/libay.make b/apps/codecs/libgme/libay.make new file mode 100644 index 0000000000..5eee8ac1e4 --- /dev/null +++ b/apps/codecs/libgme/libay.make @@ -0,0 +1,21 @@ + +# libay +AYLIB := $(CODECDIR)/libay.a +AYLIB_SRC := $(call preprocess, $(APPSDIR)/codecs/libgme/AYSOURCES) +AYLIB_OBJ := $(call c2obj, $(AYLIB_SRC)) +OTHER_SRC += $(AYLIB_SRC) + +$(AYLIB): $(AYLIB_OBJ) + $(SILENT)$(shell rm -f $@) + $(call PRINTS,AR $(@F))$(AR) rcs $@ $^ >/dev/null + +AYFLAGS = $(filter-out -O%,$(CODECFLAGS)) -fno-strict-aliasing -DGME_AY_TYPE +ifeq ($(CPU),arm) + AYFLAGS += -O3 +else + AYFLAGS += -O2 +endif + +$(CODECDIR)/libgme/%.o: $(ROOTDIR)/apps/codecs/libgme/%.c + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) $(AYFLAGS) -c $< -o $@ diff --git a/apps/codecs/libgme/libgbs.make b/apps/codecs/libgme/libgbs.make new file mode 100644 index 0000000000..cf6ff01274 --- /dev/null +++ b/apps/codecs/libgme/libgbs.make @@ -0,0 +1,21 @@ + +# libgbs +GBSLIB := $(CODECDIR)/libgbs.a +GBSLIB_SRC := $(call preprocess, $(APPSDIR)/codecs/libgme/GBSSOURCES) +GBSLIB_OBJ := $(call c2obj, $(GBSLIB_SRC)) +OTHER_SRC += $(GBSLIB_SRC) + +$(GBSLIB): $(GBSLIB_OBJ) + $(SILENT)$(shell rm -f $@) + $(call PRINTS,AR $(@F))$(AR) rcs $@ $^ >/dev/null + +GBSFLAGS = $(filter-out -O%,$(CODECFLAGS)) -fno-strict-aliasing -DGME_GBS_TYPE +ifeq ($(CPU),arm) + GBSFLAGS += -O3 +else + GBSFLAGS += -O2 +endif + +$(CODECDIR)/libgme/%.o: $(ROOTDIR)/apps/codecs/libgme/%.c + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) $(GBSFLAGS) -c $< -o $@ diff --git a/apps/codecs/libgme/libhes.make b/apps/codecs/libgme/libhes.make new file mode 100644 index 0000000000..e0018565fb --- /dev/null +++ b/apps/codecs/libgme/libhes.make @@ -0,0 +1,21 @@ + +# libhes +HESLIB := $(CODECDIR)/libhes.a +HESLIB_SRC := $(call preprocess, $(APPSDIR)/codecs/libgme/HESSOURCES) +HESLIB_OBJ := $(call c2obj, $(HESLIB_SRC)) +OTHER_SRC += $(HESLIB_SRC) + +$(HESLIB): $(HESLIB_OBJ) + $(SILENT)$(shell rm -f $@) + $(call PRINTS,AR $(@F))$(AR) rcs $@ $^ >/dev/null + +HESFLAGS = $(filter-out -O%,$(CODECFLAGS)) -fno-strict-aliasing -DGME_HES_TYPE +ifeq ($(CPU),arm) + HESFLAGS += -O3 +else + HESFLAGS += -O2 +endif + +$(CODECDIR)/libgme/%.o: $(ROOTDIR)/apps/codecs/libgme/%.c + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) $(HESFLAGS) -c $< -o $@ diff --git a/apps/codecs/libgme/libkss.make b/apps/codecs/libgme/libkss.make new file mode 100644 index 0000000000..0e2dd54bc2 --- /dev/null +++ b/apps/codecs/libgme/libkss.make @@ -0,0 +1,21 @@ + +# libkss +KSSLIB := $(CODECDIR)/libkss.a +KSSLIB_SRC := $(call preprocess, $(APPSDIR)/codecs/libgme/KSSSOURCES) +KSSLIB_OBJ := $(call c2obj, $(KSSLIB_SRC)) +OTHER_SRC += $(KSSLIB_SRC) + +$(KSSLIB): $(KSSLIB_OBJ) + $(SILENT)$(shell rm -f $@) + $(call PRINTS,AR $(@F))$(AR) rcs $@ $^ >/dev/null + +KSSFLAGS = $(filter-out -O%,$(CODECFLAGS)) -fno-strict-aliasing -DGME_KSS_TYPE +ifeq ($(CPU),arm) + KSSFLAGS += -O3 +else + KSSFLAGS += -O2 +endif + +$(CODECDIR)/libgme/%.o: $(ROOTDIR)/apps/codecs/libgme/%.c + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) $(KSSFLAGS) -c $< -o $@ diff --git a/apps/codecs/libgme/libnsf.make b/apps/codecs/libgme/libnsf.make new file mode 100644 index 0000000000..8b9df7526f --- /dev/null +++ b/apps/codecs/libgme/libnsf.make @@ -0,0 +1,21 @@ + +# libnsf +NSFLIB := $(CODECDIR)/libnsf.a +NSFLIB_SRC := $(call preprocess, $(APPSDIR)/codecs/libgme/NSFSOURCES) +NSFLIB_OBJ := $(call c2obj, $(NSFLIB_SRC)) +OTHER_SRC += $(NSFLIB_SRC) + +$(NSFLIB): $(NSFLIB_OBJ) + $(SILENT)$(shell rm -f $@) + $(call PRINTS,AR $(@F))$(AR) rcs $@ $^ >/dev/null + +NSFFLAGS = $(filter-out -O%,$(CODECFLAGS)) -fno-strict-aliasing -DGME_NSF_TYPE +ifeq ($(CPU),arm) + NSFFLAGS += -O3 +else + NSFFLAGS += -O2 +endif + +$(CODECDIR)/libgme/%.o: $(ROOTDIR)/apps/codecs/libgme/%.c + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) $(NSFFLAGS) -c $< -o $@ diff --git a/apps/codecs/libgme/libsgc.make b/apps/codecs/libgme/libsgc.make new file mode 100644 index 0000000000..0defe788c6 --- /dev/null +++ b/apps/codecs/libgme/libsgc.make @@ -0,0 +1,21 @@ + +# libsgc +SGCLIB := $(CODECDIR)/libsgc.a +SGCLIB_SRC := $(call preprocess, $(APPSDIR)/codecs/libgme/SGCSOURCES) +SGCLIB_OBJ := $(call c2obj, $(SGCLIB_SRC)) +OTHER_SRC += $(SGCLIB_SRC) + +$(SGCLIB): $(SGCLIB_OBJ) + $(SILENT)$(shell rm -f $@) + $(call PRINTS,AR $(@F))$(AR) rcs $@ $^ >/dev/null + +SGCFLAGS = $(filter-out -O%,$(CODECFLAGS)) -fno-strict-aliasing -DGME_SGC_TYPE +ifeq ($(CPU),arm) + SGCFLAGS += -O3 +else + SGCFLAGS += -O2 +endif + +$(CODECDIR)/libgme/%.o: $(ROOTDIR)/apps/codecs/libgme/%.c + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) $(SGCFLAGS) -c $< -o $@ diff --git a/apps/codecs/libgme/libvgm.make b/apps/codecs/libgme/libvgm.make new file mode 100644 index 0000000000..f0e7cbb598 --- /dev/null +++ b/apps/codecs/libgme/libvgm.make @@ -0,0 +1,21 @@ + +# libvgm +VGMLIB := $(CODECDIR)/libvgm.a +VGMLIB_SRC := $(call preprocess, $(APPSDIR)/codecs/libgme/VGMSOURCES) +VGMLIB_OBJ := $(call c2obj, $(VGMLIB_SRC)) +OTHER_SRC += $(VGMLIB_SRC) + +$(VGMLIB): $(VGMLIB_OBJ) + $(SILENT)$(shell rm -f $@) + $(call PRINTS,AR $(@F))$(AR) rcs $@ $^ >/dev/null + +VGMFLAGS = $(filter-out -O%,$(CODECFLAGS)) -fno-strict-aliasing -DGME_VGM_TYPE +ifeq ($(CPU),arm) + VGMFLAGS += -O3 +else + VGMFLAGS += -O2 +endif + +$(CODECDIR)/libgme/%.o: $(ROOTDIR)/apps/codecs/libgme/%.c + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) $(VGMFLAGS) -c $< -o $@ diff --git a/apps/codecs/libgme/m3u_playlist.h b/apps/codecs/libgme/m3u_playlist.h new file mode 100644 index 0000000000..06a5d3024b --- /dev/null +++ b/apps/codecs/libgme/m3u_playlist.h @@ -0,0 +1,31 @@ +// M3U entries parser, with support for subtrack information + +// Game_Music_Emu 0.5.2 +#ifndef M3U_PLAYLIST_H +#define M3U_PLAYILST_H + +#include "blargg_common.h" + +struct entry_t +{ + unsigned char track; // 1-based + int length; // milliseconds +}; + +/* Short version of the m3u playlist */ +struct M3u_Playlist +{ + unsigned char size; + struct entry_t *entries; +}; + +static inline void M3u_load_data(struct M3u_Playlist* this, void *addr) +{ + if( addr == NULL ) return; + /* m3u entries data must be at offset 100, + the first 99 bytes are used by metadata info */ + this->size = *(unsigned char *)(addr + 99); + this->entries = (struct entry_t *)(addr+100); +} + +#endif diff --git a/apps/codecs/libgme/msxtypes.h b/apps/codecs/libgme/msxtypes.h new file mode 100644 index 0000000000..6224e0760c --- /dev/null +++ b/apps/codecs/libgme/msxtypes.h @@ -0,0 +1,36 @@ +#ifndef MSX_TYPES +#define MSX_TYPES + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifdef __GNUC__ +#define __int64 long long +#endif + +#ifdef _WIN32 +#define DIR_SEPARATOR "\\" +#else +#define DIR_SEPARATOR "/" +#endif + +/* So far, only support for MSVC types + */ +typedef unsigned char UInt8; +#ifndef __CARBON__ +typedef unsigned short UInt16; +typedef unsigned int UInt32; +typedef unsigned __int64 UInt64; +#endif +typedef signed char Int8; +typedef signed short Int16; +typedef signed int Int32; + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/apps/codecs/libgme/multi_buffer.c b/apps/codecs/libgme/multi_buffer.c new file mode 100644 index 0000000000..26cb8cdec6 --- /dev/null +++ b/apps/codecs/libgme/multi_buffer.c @@ -0,0 +1,226 @@ +// Blip_Buffer 0.4.1. http://www.slack.net/~ant/ + +#include "multi_buffer.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +// Stereo_Buffer + +void Buffer_init( struct Stereo_Buffer* this ) +{ + Blip_init( &this->bufs [0] ); + Blip_init( &this->bufs [1] ); + Blip_init( &this->bufs [2] ); + + this->chan.center = &this->bufs [0]; + this->chan.left = &this->bufs [1]; + this->chan.right = &this->bufs [2]; + + this->length_ = 0; + this->sample_rate_ = 0; + this->channels_changed_count_ = 1; + this->samples_per_frame_ = 2; +} + +blargg_err_t Buffer_set_sample_rate( struct Stereo_Buffer* this, long rate, int msec ) +{ + int i; + for ( i = 0; i < buf_count; i++ ) + RETURN_ERR( Blip_set_sample_rate( &this->bufs[i], rate, msec ) ); + + this->sample_rate_ = Blip_sample_rate( &this->bufs [0] ); + this->length_ = Blip_length( &this->bufs [0] ); + return 0; +} + +void Buffer_clock_rate( struct Stereo_Buffer* this, long rate ) +{ + int i; + for ( i = 0; i < buf_count; i++ ) + Blip_set_clock_rate( &this->bufs [i], rate ); +} + +void Buffer_bass_freq( struct Stereo_Buffer* this, int bass ) +{ + unsigned i; + for ( i = 0; i < buf_count; i++ ) + Blip_bass_freq( &this->bufs [i], bass ); +} + +struct channel_t Buffer_channel( struct Stereo_Buffer* this ) +{ + return this->chan; +} + +void Buffer_clear( struct Stereo_Buffer* this ) +{ + this->stereo_added = 0; + this->was_stereo = false; + int i; + for ( i = 0; i < buf_count; i++ ) + Blip_clear( &this->bufs [i], 1 ); +} + +void Buffer_end_frame( struct Stereo_Buffer* this, blip_time_t clock_count ) +{ + this->stereo_added = 0; + unsigned i; + for ( i = 0; i < buf_count; i++ ) + { + this->stereo_added |= Blip_clear_modified( &this->bufs [i] ) << i; + Blip_end_frame( &this->bufs [i], clock_count ); + } +} + +long Buffer_read_samples( struct Stereo_Buffer* this, blip_sample_t* out, long count ) +{ + require( !(count & 1) ); // count must be even + count = (unsigned) count / 2; + + long avail = Blip_samples_avail( &this->bufs [0] ); + if ( count > avail ) + count = avail; + if ( count ) + { + int bufs_used = this->stereo_added | this->was_stereo; + //dprintf( "%X\n", bufs_used ); + if ( bufs_used <= 1 ) + { + Buffer_mix_mono( this, out, count ); + Blip_remove_samples( &this->bufs [0], count ); + Blip_remove_silence( &this->bufs [1], count ); + Blip_remove_silence( &this->bufs [2], count ); + } + else if ( bufs_used & 1 ) + { + Buffer_mix_stereo( this, out, count ); + Blip_remove_samples( &this->bufs [0], count ); + Blip_remove_samples( &this->bufs [1], count ); + Blip_remove_samples( &this->bufs [2], count ); + } + else + { + Buffer_mix_stereo_no_center( this, out, count ); + Blip_remove_silence( &this->bufs [0], count ); + Blip_remove_samples( &this->bufs [1], count ); + Blip_remove_samples( &this->bufs [2], count ); + } + + // to do: this might miss opportunities for optimization + if ( !Blip_samples_avail( &this->bufs [0] ) ) + { + this->was_stereo = this->stereo_added; + this->stereo_added = 0; + } + } + + return count * 2; +} + +unsigned Buffer_channels_changed_count( struct Stereo_Buffer* this ) +{ + return this->channels_changed_count_; +} + +void Buffer_channels_changed( struct Stereo_Buffer* this ) +{ + this->channels_changed_count_++; +} + +void Buffer_mix_stereo( struct Stereo_Buffer* this, blip_sample_t* out_, blargg_long count ) +{ + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( this->bufs [1] ); + BLIP_READER_BEGIN( left, this->bufs [1] ); + BLIP_READER_BEGIN( right, this->bufs [2] ); + BLIP_READER_BEGIN( center, this->bufs [0] ); + + for ( ; count; --count ) + { + int c = BLIP_READER_READ( center ); + blargg_long l = c + BLIP_READER_READ( left ); + blargg_long r = c + BLIP_READER_READ( right ); + if ( (int16_t) l != l ) + l = 0x7FFF - (l >> 24); + + BLIP_READER_NEXT( center, bass ); + if ( (int16_t) r != r ) + r = 0x7FFF - (r >> 24); + + BLIP_READER_NEXT( left, bass ); + BLIP_READER_NEXT( right, bass ); + + out [0] = l; + out [1] = r; + out += 2; + } + + BLIP_READER_END( center, this->bufs [0] ); + BLIP_READER_END( right, this->bufs [2] ); + BLIP_READER_END( left, this->bufs [1] ); +} + +void Buffer_mix_stereo_no_center( struct Stereo_Buffer* this, blip_sample_t* out_, blargg_long count ) +{ + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( this->bufs [1] ); + BLIP_READER_BEGIN( left, this->bufs [1] ); + BLIP_READER_BEGIN( right, this->bufs [2] ); + + for ( ; count; --count ) + { + blargg_long l = BLIP_READER_READ( left ); + if ( (int16_t) l != l ) + l = 0x7FFF - (l >> 24); + + blargg_long r = BLIP_READER_READ( right ); + if ( (int16_t) r != r ) + r = 0x7FFF - (r >> 24); + + BLIP_READER_NEXT( left, bass ); + BLIP_READER_NEXT( right, bass ); + + out [0] = l; + out [1] = r; + out += 2; + } + + BLIP_READER_END( right, this->bufs [2] ); + BLIP_READER_END( left, this->bufs [1] ); +} + +void Buffer_mix_mono( struct Stereo_Buffer* this, blip_sample_t* out_, blargg_long count ) +{ + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( this->bufs [0] ); + BLIP_READER_BEGIN( center, this->bufs [0] ); + + for ( ; count; --count ) + { + blargg_long s = BLIP_READER_READ( center ); + if ( (int16_t) s != s ) + s = 0x7FFF - (s >> 24); + + BLIP_READER_NEXT( center, bass ); + out [0] = s; + out [1] = s; + out += 2; + } + + BLIP_READER_END( center, this->bufs [0] ); +} diff --git a/apps/codecs/libgme/multi_buffer.h b/apps/codecs/libgme/multi_buffer.h new file mode 100644 index 0000000000..26f302380c --- /dev/null +++ b/apps/codecs/libgme/multi_buffer.h @@ -0,0 +1,72 @@ +// Multi-channel sound buffer interface, and basic mono and stereo buffers + +// Blip_Buffer 0.4.1 +#ifndef MULTI_BUFFER_H +#define MULTI_BUFFER_H + +#include "blargg_common.h" +#include "blip_buffer.h" + +// Get indexed channel, from 0 to channel count - 1 +struct channel_t { + struct Blip_Buffer* center; + struct Blip_Buffer* left; + struct Blip_Buffer* right; +}; + +enum { type_index_mask = 0xFF }; +enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type }; +enum { buf_count = 3 }; + +struct Stereo_Buffer { + struct Blip_Buffer bufs [buf_count]; + struct channel_t chan; + int stereo_added; + int was_stereo; + + unsigned channels_changed_count_; + long sample_rate_; + int length_; + int samples_per_frame_; +}; + +// Initializes Stereo_Buffer structure +void Buffer_init( struct Stereo_Buffer* this ); + +blargg_err_t Buffer_set_sample_rate( struct Stereo_Buffer* this, long, int msec ); +void Buffer_clock_rate( struct Stereo_Buffer* this, long ); +void Buffer_bass_freq( struct Stereo_Buffer* this, int ); +void Buffer_clear( struct Stereo_Buffer* this ); +struct channel_t Buffer_channel( struct Stereo_Buffer* this ); +void Buffer_end_frame( struct Stereo_Buffer* this, blip_time_t ) ICODE_ATTR; + +long Buffer_read_samples( struct Stereo_Buffer* this, blip_sample_t*, long ) ICODE_ATTR; + +// Count of changes to channel configuration. Incremented whenever +// a change is made to any of the Blip_Buffers for any channel. +unsigned Buffer_channels_changed_count( struct Stereo_Buffer* this ) ICODE_ATTR; +void Buffer_channels_changed( struct Stereo_Buffer* this ) ICODE_ATTR; + +void Buffer_mix_stereo_no_center( struct Stereo_Buffer* this, blip_sample_t*, blargg_long ) ICODE_ATTR; +void Buffer_mix_stereo( struct Stereo_Buffer* this, blip_sample_t*, blargg_long ) ICODE_ATTR; +void Buffer_mix_mono( struct Stereo_Buffer* this, blip_sample_t*, blargg_long ) ICODE_ATTR; + +// Number of samples per output frame (1 = mono, 2 = stereo) +static inline int Buffer_samples_per_frame( struct Stereo_Buffer* this ) +{ + return this->samples_per_frame_; +} + +// See Blip_Buffer.h +static inline long Buffer_sample_rate( struct Stereo_Buffer* this ) +{ + return this->sample_rate_; +} + +// Length of buffer, in milliseconds +static inline int Buffer_length( struct Stereo_Buffer* this ) +{ + return this->length_; +} + +#endif diff --git a/apps/codecs/libgme/nes_apu.c b/apps/codecs/libgme/nes_apu.c new file mode 100644 index 0000000000..8f1f37645e --- /dev/null +++ b/apps/codecs/libgme/nes_apu.c @@ -0,0 +1,393 @@ +// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/ + +#include "nes_apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const amp_range = 15; + +void Apu_init( struct Nes_Apu* this ) +{ + this->tempo_ = 1.0; + this->dmc.apu = this; + this->dmc.prg_reader = NULL; + this->irq_notifier_ = NULL; + + Synth_init( &this->square_synth ); + Synth_init( &this->triangle.synth ); + Synth_init( &this->noise.synth ); + Synth_init( &this->dmc.synth ); + + Square_set_synth( &this->square1, &this->square_synth ); + Square_set_synth( &this->square2, &this->square_synth ); + + this->oscs [0] = &this->square1.osc; + this->oscs [1] = &this->square2.osc; + this->oscs [2] = &this->triangle.osc; + this->oscs [3] = &this->noise.osc; + this->oscs [4] = &this->dmc.osc; + + Apu_output( this, NULL ); + Apu_volume( this, 1.0 ); + Apu_reset( this, false, 0 ); +} + +static double nonlinear_tnd_gain( void ) { return 0.75; } +void Apu_enable_nonlinear( struct Nes_Apu* this, double v ) +{ + this->dmc.nonlinear = true; + Synth_volume( &this->square_synth, 1.3 * 0.25751258 / 0.742467605 * 0.25 / amp_range * v ); + + const double tnd = 0.48 / 202 * nonlinear_tnd_gain(); + Synth_volume( &this->triangle.synth, 3.0 * tnd ); + Synth_volume( &this->noise.synth, 2.0 * tnd ); + Synth_volume( &this->dmc.synth, tnd ); + + this->square1 .osc.last_amp = 0; + this->square2 .osc.last_amp = 0; + this->triangle.osc.last_amp = 0; + this->noise .osc.last_amp = 0; + this->dmc .osc.last_amp = 0; +} + +void Apu_volume( struct Nes_Apu* this, double v ) +{ + this->dmc.nonlinear = false; + Synth_volume( &this->square_synth, 0.1128 / amp_range * v ); + Synth_volume( &this->triangle.synth,0.12765 / amp_range * v ); + Synth_volume( &this->noise.synth, 0.0741 / amp_range * v ); + Synth_volume( &this->dmc.synth, 0.42545 / 127 * v ); +} + +void Apu_output( struct Nes_Apu* this, struct Blip_Buffer* buffer ) +{ + int i; + for ( i = 0; i < apu_osc_count; i++ ) + Apu_osc_output( this, i, buffer ); +} + +void Apu_set_tempo( struct Nes_Apu* this, double t ) +{ + this->tempo_ = t; + this->frame_period = (this->dmc.pal_mode ? 8314 : 7458); + if ( t != 1.0 ) + this->frame_period = (int) (this->frame_period / t) & ~1; // must be even +} + +void Apu_reset( struct Nes_Apu* this, bool pal_mode, int initial_dmc_dac ) +{ + this->dmc.pal_mode = pal_mode; + Apu_set_tempo( this, this->tempo_ ); + + Square_reset( &this->square1 ); + Square_reset( &this->square2 ); + Triangle_reset( &this->triangle ); + Noise_reset( &this->noise ); + Dmc_reset( &this->dmc ); + + this->last_time = 0; + this->last_dmc_time = 0; + this->osc_enables = 0; + this->irq_flag = false; + this->earliest_irq_ = apu_no_irq; + this->frame_delay = 1; + Apu_write_register( this, 0, 0x4017, 0x00 ); + Apu_write_register( this, 0, 0x4015, 0x00 ); + + addr_t addr; + for ( addr = apu_io_addr; addr <= 0x4013; addr++ ) + Apu_write_register( this, 0, addr, (addr & 3) ? 0x00 : 0x10 ); + + this->dmc.dac = initial_dmc_dac; + if ( !this->dmc.nonlinear ) + this->triangle.osc.last_amp = 15; + if ( !this->dmc.nonlinear ) // TODO: remove? + this->dmc.osc.last_amp = initial_dmc_dac; // prevent output transition +} + +void Apu_irq_changed( struct Nes_Apu* this ) +{ + nes_time_t new_irq = this->dmc.next_irq; + if ( this->dmc.irq_flag | this->irq_flag ) { + new_irq = 0; + } + else if ( new_irq > this->next_irq ) { + new_irq = this->next_irq; + } + + if ( new_irq != this->earliest_irq_ ) { + this->earliest_irq_ = new_irq; + if ( this->irq_notifier_ ) + this->irq_notifier_( this->irq_data ); + } +} + +// frames + +void Apu_run_until( struct Nes_Apu* this, nes_time_t end_time ) +{ + require( end_time >= this->last_dmc_time ); + if ( end_time > Apu_next_dmc_read_time( this ) ) + { + nes_time_t start = this->last_dmc_time; + this->last_dmc_time = end_time; + Dmc_run( &this->dmc, start, end_time ); + } +} + +void run_until_( struct Nes_Apu* this, nes_time_t end_time ) +{ + require( end_time >= this->last_time ); + + if ( end_time == this->last_time ) + return; + + if ( this->last_dmc_time < end_time ) + { + nes_time_t start = this->last_dmc_time; + this->last_dmc_time = end_time; + Dmc_run( &this->dmc, start, end_time ); + } + + while ( true ) + { + // earlier of next frame time or end time + nes_time_t time = this->last_time + this->frame_delay; + if ( time > end_time ) + time = end_time; + this->frame_delay -= time - this->last_time; + + // run oscs to present + Square_run( &this->square1, this->last_time, time ); + Square_run( &this->square2, this->last_time, time ); + Triangle_run( &this->triangle, this->last_time, time ); + Noise_run( &this->noise, this->last_time, time ); + this->last_time = time; + + if ( time == end_time ) + break; // no more frames to run + + // take frame-specific actions + this->frame_delay = this->frame_period; + switch ( this->frame++ ) + { + case 0: + if ( !(this->frame_mode & 0xC0) ) { + this->next_irq = time + this->frame_period * 4 + 2; + this->irq_flag = true; + } + // fall through + case 2: + // clock length and sweep on frames 0 and 2 + Osc_clock_length( &this->square1.osc, 0x20 ); + Osc_clock_length( &this->square2.osc, 0x20 ); + Osc_clock_length( &this->noise.osc, 0x20 ); + Osc_clock_length( &this->triangle.osc, 0x80 ); // different bit for halt flag on triangle + + Square_clock_sweep( &this->square1, -1 ); + Square_clock_sweep( &this->square2, 0 ); + + // frame 2 is slightly shorter in mode 1 + if ( this->dmc.pal_mode && this->frame == 3 ) + this->frame_delay -= 2; + break; + + case 1: + // frame 1 is slightly shorter in mode 0 + if ( !this->dmc.pal_mode ) + this->frame_delay -= 2; + break; + + case 3: + this->frame = 0; + + // frame 3 is almost twice as long in mode 1 + if ( this->frame_mode & 0x80 ) + this->frame_delay += this->frame_period - (this->dmc.pal_mode ? 2 : 6); + break; + } + + // clock envelopes and linear counter every frame + Triangle_clock_linear_counter( &this->triangle ); + Square_clock_envelope( &this->square1 ); + Square_clock_envelope( &this->square2 ); + Noise_clock_envelope( &this->noise ); + } +} + +static inline void zero_apu_osc( struct Nes_Osc* osc, struct Blip_Synth* synth, nes_time_t time ) +{ + struct Blip_Buffer* output = osc->output; + int last_amp = osc->last_amp; + osc->last_amp = 0; + if ( output && last_amp ) + Synth_offset( synth, time, -osc->last_amp, output ); +} + +void Apu_end_frame( struct Nes_Apu* this, nes_time_t end_time ) +{ + if ( end_time > this->last_time ) + run_until_( this, end_time ); + + if ( this->dmc.nonlinear ) + { + zero_apu_osc( &this->square1.osc, this->square1.synth, this->last_time ); + zero_apu_osc( &this->square2.osc, this->square2.synth, this->last_time ); + zero_apu_osc( &this->triangle.osc, &this->triangle.synth, this->last_time ); + zero_apu_osc( &this->noise.osc, &this->noise.synth, this->last_time ); + zero_apu_osc( &this->dmc.osc, &this->dmc.synth, this->last_time ); + } + + // make times relative to new frame + this->last_time -= end_time; + require( this->last_time >= 0 ); + + this->last_dmc_time -= end_time; + require( this->last_dmc_time >= 0 ); + + if ( this->next_irq != apu_no_irq ) { + this->next_irq -= end_time; + check( this->next_irq >= 0 ); + } + if ( this->dmc.next_irq != apu_no_irq ) { + this->dmc.next_irq -= end_time; + check( this->dmc.next_irq >= 0 ); + } + if ( this->earliest_irq_ != apu_no_irq ) { + this->earliest_irq_ -= end_time; + if ( this->earliest_irq_ < 0 ) + this->earliest_irq_ = 0; + } +} + +// registers + +static const unsigned char length_table [0x20] ICONST_ATTR = { + 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, + 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, + 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, + 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E +}; + +void Apu_write_register( struct Nes_Apu* this, nes_time_t time, addr_t addr, int data ) +{ + require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx) + require( (unsigned) data <= 0xFF ); + + // Ignore addresses outside range + if ( (unsigned) (addr - apu_io_addr) >= apu_io_size ) + return; + + run_until_( this, time ); + + if ( addr < 0x4014 ) + { + // Write to channel + int osc_index = (addr - apu_io_addr) >> 2; + struct Nes_Osc* osc = this->oscs [osc_index]; + + int reg = addr & 3; + osc->regs [reg] = data; + osc->reg_written [reg] = true; + + if ( osc_index == 4 ) + { + // handle DMC specially + Dmc_write_register( &this->dmc, reg, data ); + } + else if ( reg == 3 ) + { + // load length counter + if ( (this->osc_enables >> osc_index) & 1 ) + osc->length_counter = length_table [(data >> 3) & 0x1F]; + + // reset square phase + if ( osc_index == 0 ) this->square1.phase = square_phase_range - 1; + else if ( osc_index == 1 ) this->square2.phase = square_phase_range - 1; + } + } + else if ( addr == 0x4015 ) + { + // Channel enables + int i; + for ( i = apu_osc_count; i--; ) + if ( !((data >> i) & 1) ) + this->oscs [i]->length_counter = 0; + + bool recalc_irq = this->dmc.irq_flag; + this->dmc.irq_flag = false; + + int old_enables = this->osc_enables; + this->osc_enables = data; + if ( !(data & 0x10) ) { + this->dmc.next_irq = apu_no_irq; + recalc_irq = true; + } + else if ( !(old_enables & 0x10) ) { + Dmc_start( &this->dmc ); // dmc just enabled + } + + if ( recalc_irq ) + Apu_irq_changed( this ); + } + else if ( addr == 0x4017 ) + { + // Frame mode + this->frame_mode = data; + + bool irq_enabled = !(data & 0x40); + this->irq_flag &= irq_enabled; + this->next_irq = apu_no_irq; + + // mode 1 + this->frame_delay = (this->frame_delay & 1); + this->frame = 0; + + if ( !(data & 0x80) ) + { + // mode 0 + this->frame = 1; + this->frame_delay += this->frame_period; + if ( irq_enabled ) + this->next_irq = time + this->frame_delay + this->frame_period * 3 + 1; + } + + Apu_irq_changed( this ); + } +} + +int Apu_read_status( struct Nes_Apu* this, nes_time_t time ) +{ + run_until_( this, time - 1 ); + + int result = (this->dmc.irq_flag << 7) | (this->irq_flag << 6); + + int i; + for ( i = 0; i < apu_osc_count; i++ ) + if ( this->oscs [i]->length_counter ) + result |= 1 << i; + + run_until_( this, time ); + + if ( this->irq_flag ) + { + result |= 0x40; + this->irq_flag = false; + Apu_irq_changed( this ); + } + + //debug_printf( "%6d/%d Read $4015->$%02X\n", frame_delay, frame, result ); + + return result; +} diff --git a/apps/codecs/libgme/nes_apu.h b/apps/codecs/libgme/nes_apu.h new file mode 100644 index 0000000000..6a2c2805e1 --- /dev/null +++ b/apps/codecs/libgme/nes_apu.h @@ -0,0 +1,134 @@ +// NES 2A03 APU sound chip emulator + +// Nes_Snd_Emu 0.1.8 +#ifndef NES_APU_H +#define NES_APU_H + +#include "blargg_common.h" +#include "nes_oscs.h" + +enum { apu_status_addr = 0x4015 }; +enum { apu_osc_count = 5 }; +enum { apu_no_irq = INT_MAX / 2 + 1 }; +enum { apu_irq_waiting = 0 }; + +enum { apu_io_addr = 0x4000 }; +enum { apu_io_size = 0x18 }; + +struct apu_state_t; + +struct Nes_Apu { + nes_time_t last_dmc_time; + int osc_enables; + + struct Nes_Osc* oscs [apu_osc_count]; + struct Nes_Square square1; + struct Nes_Square square2; + struct Nes_Noise noise; + struct Nes_Triangle triangle; + struct Nes_Dmc dmc; + + double tempo_; + nes_time_t last_time; // has been run until this time in current frame + nes_time_t earliest_irq_; + nes_time_t next_irq; + int frame_period; + int frame_delay; // cycles until frame counter runs next + int frame; // current frame (0-3) + int frame_mode; + bool irq_flag; + + void (*irq_notifier_)( void* user_data ); + void* irq_data; + + Synth square_synth; // shared by squares +}; + +// Init Nes apu +void Apu_init( struct Nes_Apu* this ); + +// Set buffer to generate all sound into, or disable sound if NULL +void Apu_output( struct Nes_Apu* this, struct Blip_Buffer* ) ICODE_ATTR; + +// All time values are the number of cpu clock cycles relative to the +// beginning of the current time frame. Before resetting the cpu clock +// count, call end_frame( last_cpu_time ). + +// Write to register (0x4000-0x4017, except 0x4014 and 0x4016) +void Apu_write_register( struct Nes_Apu* this, nes_time_t, addr_t, int data ) ICODE_ATTR; + +// Read from status register at 0x4015 +int Apu_read_status( struct Nes_Apu* this, nes_time_t ) ICODE_ATTR; + +// Run all oscillators up to specified time, end current time frame, then +// start a new time frame at time 0. Time frames have no effect on emulation +// and each can be whatever length is convenient. +void Apu_end_frame( struct Nes_Apu* this, nes_time_t ) ICODE_ATTR; + +// Additional optional features (can be ignored without any problem) + +// Reset internal frame counter, registers, and all oscillators. +// Use PAL timing if pal_timing is true, otherwise use NTSC timing. +// Set the DMC oscillator's initial DAC value to initial_dmc_dac without +// any audible click. +void Apu_reset( struct Nes_Apu* this, bool pal_mode, int initial_dmc_dac ); + +// Adjust frame period +void Apu_set_tempo( struct Nes_Apu* this, double ); + +// Set overall volume (default is 1.0) +void Apu_volume( struct Nes_Apu* this, double ); + +// Run DMC until specified time, so that any DMC memory reads can be +// accounted for (i.e. inserting cpu wait states). +void Apu_run_until( struct Nes_Apu* this, nes_time_t ) ICODE_ATTR; + +// Set sound output of specific oscillator to buffer. If buffer is NULL, +// the specified oscillator is muted and emulation accuracy is reduced. +// The oscillators are indexed as follows: 0) Square 1, 1) Square 2, +// 2) Triangle, 3) Noise, 4) DMC. +static inline void Apu_osc_output( struct Nes_Apu* this, int osc, struct Blip_Buffer* buf ) +{ + assert( (unsigned) osc < apu_osc_count ); + this->oscs [osc]->output = buf; +} + +// Set memory reader callback used by DMC oscillator to fetch samples. +// When callback is invoked, 'user_data' is passed unchanged as the +// first parameter. +static inline void Apu_dmc_reader( struct Nes_Apu* this, int (*func)( void*, addr_t ), void* user_data ) +{ + this->dmc.prg_reader_data = user_data; + this->dmc.prg_reader = func; +} + +// Set IRQ time callback that is invoked when the time of earliest IRQ +// may have changed, or NULL to disable. When callback is invoked, +// 'user_data' is passed unchanged as the first parameter. +static inline void Apu_irq_notifier( struct Nes_Apu* this, void (*func)( void* user_data ), void* user_data ) +{ + this->irq_notifier_ = func; + this->irq_data = user_data; +} + +// Count number of DMC reads that would occur if 'run_until( t )' were executed. +// If last_read is not NULL, set *last_read to the earliest time that +// 'count_dmc_reads( time )' would result in the same result. +static inline int Apu_count_dmc_reads( struct Nes_Apu* this, nes_time_t time, nes_time_t* last_read ) +{ + return Dmc_count_reads( &this->dmc, time, last_read ); +} + +static inline nes_time_t Dmc_next_read_time( struct Nes_Dmc* this ) +{ + if ( this->osc.length_counter == 0 ) + return apu_no_irq; // not reading + + return this->apu->last_dmc_time + this->osc.delay + (long) (this->bits_remain - 1) * this->period; +} + +// Time when next DMC memory read will occur +static inline nes_time_t Apu_next_dmc_read_time( struct Nes_Apu* this ) { return Dmc_next_read_time( &this->dmc ); } +void Apu_irq_changed( struct Nes_Apu* this ) ICODE_ATTR; + +#endif diff --git a/apps/codecs/libgme/nes_cpu.c b/apps/codecs/libgme/nes_cpu.c new file mode 100644 index 0000000000..d255cf5485 --- /dev/null +++ b/apps/codecs/libgme/nes_cpu.c @@ -0,0 +1,62 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "nes_cpu.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +inline void set_code_page( struct Nes_Cpu* this, int i, void const* p ) +{ + byte const* p2 = STATIC_CAST(byte const*,p) - NES_CPU_OFFSET( i * page_size ); + this->cpu_state->code_map [i] = p2; + this->cpu_state_.code_map [i] = p2; +} + +void Cpu_map_code( struct Nes_Cpu* this, addr_t start, int size, void const* data, int mirror_size ) +{ + // address range must begin and end on page boundaries + require( start % page_size == 0 ); + require( size % page_size == 0 ); + require( start + size <= 0x10000 ); + require( mirror_size % page_size == 0 ); + + int offset; + for ( offset = 0; offset < size; offset += page_size ) + set_code_page( this, NES_CPU_PAGE( start + offset ), + STATIC_CAST(char const*,data) + (offset & ((unsigned) mirror_size - 1)) ); +} + +void Cpu_reset( struct Nes_Cpu* this, void const* unmapped_page ) +{ + check( this->cpu_state == &this->cpu_state_ ); + this->cpu_state = &this->cpu_state_; + + this->r.flags = irq_inhibit_mask; + this->r.sp = 0xFF; + this->r.pc = 0; + this->r.a = 0; + this->r.x = 0; + this->r.y = 0; + + this->cpu_state_.time = 0; + this->cpu_state_.base = 0; + this->irq_time = future_time; + this->end_time = future_time; + + set_code_page( this, page_count, unmapped_page ); + Cpu_map_code( this, 0, 0x10000, unmapped_page, page_size ); + + blargg_verify_byte_order(); +} diff --git a/apps/codecs/libgme/nes_cpu.h b/apps/codecs/libgme/nes_cpu.h new file mode 100644 index 0000000000..7129ffd925 --- /dev/null +++ b/apps/codecs/libgme/nes_cpu.h @@ -0,0 +1,106 @@ +// NES cpu emulator + +// Game_Music_Emu 0.6-pre +#ifndef NES_CPU_H +#define NES_CPU_H + +#include "blargg_common.h" +#include "blargg_source.h" + +typedef int nes_time_t; +typedef int addr_t; + +enum { page_bits = 11 }; +enum { page_size = 1 << page_bits }; +enum { page_count = 0x10000 >> page_bits }; + +// Unmapped page should be filled with this +enum { halt_opcode = 0x22 }; + +enum { future_time = INT_MAX/2 + 1 }; +enum { irq_inhibit_mask = 0x04 }; + +// Can read this many bytes past end of a page +enum { cpu_padding = 8 }; + +struct registers_t { + uint16_t pc; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t flags; + uint8_t sp; +}; + +struct cpu_state_t { + uint8_t const* code_map [page_count + 1]; + nes_time_t base; + int time; +}; + +struct Nes_Cpu { + // NES 6502 registers. NOT kept updated during emulation. + struct registers_t r; + nes_time_t irq_time; + nes_time_t end_time; + + struct cpu_state_t* cpu_state; // points to cpu_state_ or a local copy + struct cpu_state_t cpu_state_; +}; + +static inline void Cpu_init( struct Nes_Cpu* this ) { this->cpu_state = &this->cpu_state_; } + +// Clears registers and maps all pages to unmapped_page +void Cpu_reset( struct Nes_Cpu* this, void const* unmapped_page ); + +// Maps code memory (memory accessed via the program counter). Start and size +// must be multiple of page_size. If mirror_size is non-zero, the first +// mirror_size bytes are repeated over the range. mirror_size must be a +// multiple of page_size. +void Cpu_map_code( struct Nes_Cpu* this, addr_t start, int size, void const* code, int mirror_size ); + +// Time of beginning of next instruction to be executed +static inline nes_time_t Cpu_time( struct Nes_Cpu* this ) { return this->cpu_state->time + this->cpu_state->base; } +static inline void Cpu_set_time( struct Nes_Cpu* this, nes_time_t t ) { this->cpu_state->time = t - this->cpu_state->base; } +static inline void Cpu_adjust_time( struct Nes_Cpu* this, int delta ) { this->cpu_state->time += delta; } + +// Clocks past end (negative if before) +static inline int Cpu_time_past_end( struct Nes_Cpu* this ) { return this->cpu_state->time; } + +#define NES_CPU_PAGE( addr ) ((unsigned) (addr) >> page_bits) + +#ifdef BLARGG_NONPORTABLE + #define NES_CPU_OFFSET( addr ) (addr) +#else + #define NES_CPU_OFFSET( addr ) ((addr) & (page_size - 1)) +#endif + +// Accesses emulated memory as cpu does +static inline uint8_t const* Cpu_get_code( struct Nes_Cpu* this, addr_t addr ) +{ + return this->cpu_state_.code_map [NES_CPU_PAGE( addr )] + NES_CPU_OFFSET( addr ); +} + +static inline void Cpu_update_end_time( struct Nes_Cpu* this, nes_time_t end, nes_time_t irq ) +{ + if ( end > irq && !(this->r.flags & irq_inhibit_mask) ) + end = irq; + + this->cpu_state->time += this->cpu_state->base - end; + this->cpu_state->base = end; +} + +// Time of next IRQ +static inline void Cpu_set_irq_time( struct Nes_Cpu* this, nes_time_t t ) +{ + this->irq_time = t; + Cpu_update_end_time( this, this->end_time, t ); +} + +static inline void Cpu_set_end_time( struct Nes_Cpu* this, nes_time_t t ) +{ + this->end_time = t; + Cpu_update_end_time( this, t, this->irq_time ); +} + +#endif diff --git a/apps/codecs/libgme/nes_cpu_io.h b/apps/codecs/libgme/nes_cpu_io.h new file mode 100644 index 0000000000..4f9d416c2d --- /dev/null +++ b/apps/codecs/libgme/nes_cpu_io.h @@ -0,0 +1,94 @@ + +#include "nsf_emu.h" + +#ifndef NSF_EMU_APU_ONLY + #include "nes_namco_apu.h" + #include "nes_fds_apu.h" + #include "nes_mmc5_apu.h" +#endif + +#include "blargg_source.h" + +int Cpu_read( struct Nsf_Emu* this, nes_addr_t addr ) +{ + int result = this->cpu.low_mem [addr & 0x7FF]; + if ( addr & 0xE000 ) + { + result = *Cpu_get_code( &this->cpu, addr ); + if ( addr < sram_addr ) + { + if ( addr == status_addr ) + result = Apu_read_status( &this->apu, Cpu_time( &this->cpu ) ); + else + { + #ifndef NSF_EMU_APU_ONLY + if ( namco_enabled( this ) && addr == namco_data_reg_addr ) + return Namco_read_data( &this->namco ); + + if ( fds_enabled( this ) && (unsigned) (addr - fds_io_addr) < fds_io_size ) + return Fds_read( &this->fds, Cpu_time( &this->cpu ), addr ); + + if ( mmc5_enabled( this ) ) { + int i = addr - 0x5C00; + if ( (unsigned) i < mmc5_exram_size ) + return this->mmc5.exram [i]; + + int m = addr - 0x5205; + if ( (unsigned) m < 2 ) + return (this->mmc5_mul [0] * this->mmc5_mul [1]) >> (m * 8) & 0xFF; + } + #endif + result = addr >> 8; // simulate open bus + } + } + } + + /* if ( addr != 0x2002 ) + debug_printf( "Read unmapped $%.4X\n", (unsigned) addr ); */ + + return result; +} + +void Cpu_write( struct Nsf_Emu* this, nes_addr_t addr, int data ) +{ + int offset = addr - sram_addr; + if ( (unsigned) offset < sram_size ) + { + this->sram [offset] = data; + } + else + { + // after sram because cpu handles most low_ram accesses internally already + int temp = addr & (low_ram_size-1); // also handles wrap-around + if ( !(addr & 0xE000) ) + { + this->cpu.low_mem [temp] = data; + } + else + { + int bank = addr - banks_addr; + if ( (unsigned) bank < bank_count ) + { + Write_bank( this, bank, data ); + } + else if ( (unsigned) (addr - start_addr) <= end_addr - start_addr ) + { + Apu_write_register( &this->apu, Cpu_time( &this->cpu ), addr, data ); + } + else + { + #ifndef NSF_EMU_APU_ONLY + // 0x8000-0xDFFF is writable + int i = addr - 0x8000; + if ( fds_enabled( this ) && (unsigned) i < fdsram_size ) + fdsram( this ) [i] = data; + else + #endif + Cpu_write_misc( this, addr, data ); + } + } + } +} + +#define CPU_READ( emu, addr, time ) Cpu_read( emu, addr ) +#define CPU_WRITE( emu, addr, data, time ) Cpu_write( emu, addr, data ) diff --git a/apps/codecs/libgme/nes_cpu_run.h b/apps/codecs/libgme/nes_cpu_run.h new file mode 100644 index 0000000000..5b964d5070 --- /dev/null +++ b/apps/codecs/libgme/nes_cpu_run.h @@ -0,0 +1,1122 @@ +// NES 6502 cpu emulator run function + +#if 0 +/* Define these macros in the source file before #including this file. +- Parameters might be expressions, so they are best evaluated only once, +though they NEVER have side-effects, so multiple evaluation is OK. +- Output parameters might be a multiple-assignment expression like "a=x", +so they must NOT be parenthesized. +- Except where noted, time() and related functions will NOT work +correctly inside a macro. TIME() is always correct, and FLUSH_TIME() and +CACHE_TIME() allow the time changing functions to work. +- Macros "returning" void may use a {} statement block. */ + + // 0 <= addr <= 0xFFFF + page_size + // time functions can be used + int READ_MEM( addr_t ); + void WRITE_MEM( addr_t, int data ); + // 0 <= READ_MEM() <= 0xFF + + // 0 <= addr <= 0x1FF + int READ_LOW( addr_t ); + void WRITE_LOW( addr_t, int data ); + // 0 <= READ_LOW() <= 0xFF + + // Often-used instructions attempt these before using a normal memory access. + // Optional; defaults to READ_MEM() and WRITE_MEM() + bool CAN_READ_FAST( addr_t ); // if true, uses result of READ_FAST + void READ_FAST( addr_t, int& out ); // ALWAYS called BEFORE CAN_READ_FAST + bool CAN_WRITE_FAST( addr_t ); // if true, uses WRITE_FAST instead of WRITE_MEM + void WRITE_FAST( addr_t, int data ); + + // Used by instructions most often used to access the NES PPU (LDA abs and BIT abs). + // Optional; defaults to READ_MEM. + void READ_PPU( addr_t, int& out ); + // 0 <= out <= 0xFF + +// The following can be used within macros: + + // Current time + time_t TIME(); + + // Allows use of time functions + void FLUSH_TIME(); + + // Must be used before end of macro if FLUSH_TIME() was used earlier + void CACHE_TIME(); + +// Configuration (optional; commented behavior if defined) + + // Emulates dummy reads for indexed instructions + #define NES_CPU_DUMMY_READS 1 + + // Optimizes as if map_code( 0, 0x10000 + cpu_padding, FLAT_MEM ) is always in effect + #define FLAT_MEM my_mem_array + + // Expanded just before beginning of code, to help debugger + #define CPU_BEGIN void my_run_cpu() { + +#endif + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +// Allows MWCW debugger to step through code properly +#ifdef CPU_BEGIN + CPU_BEGIN +#endif + +// Time +#define TIME() (s_time + s.base) +#define FLUSH_TIME() {s.time = s_time - time_offset;} +#define CACHE_TIME() {s_time = s.time + time_offset;} + +// Defaults +#ifndef CAN_WRITE_FAST + #define CAN_WRITE_FAST( addr ) 0 + #define WRITE_FAST( addr, data ) +#endif + +#ifndef CAN_READ_FAST + #define CAN_READ_FAST( addr ) 0 + #define READ_FAST( addr, out ) +#endif + +#ifndef READ_PPU + #define READ_PPU( addr, out )\ + {\ + FLUSH_TIME();\ + out = READ_MEM( addr );\ + CACHE_TIME();\ + } +#endif + +#define READ_STACK READ_LOW +#define WRITE_STACK WRITE_LOW + +// Dummy reads +#ifdef NES_CPU_DUMMY_READS + // TODO: optimize time handling + #define DUMMY_READ( addr, idx ) \ + if ( (addr & 0xFF) < idx )\ + {\ + int const time_offset = 1;\ + FLUSH_TIME();\ + READ_MEM( (addr - 0x100) );\ + CACHE_TIME();\ + } +#else + #define DUMMY_READ( addr, idx ) +#endif + +// Code +#ifdef FLAT_MEM + #define CODE_PAGE( addr ) (FLAT_MEM) + #define CODE_OFFSET( addr ) (addr) +#else + #define CODE_PAGE( addr ) (s.code_map [NES_CPU_PAGE( addr )]) + #define CODE_OFFSET( addr ) NES_CPU_OFFSET( addr ) +#endif +#define READ_CODE( addr ) (CODE_PAGE( addr ) [CODE_OFFSET( addr )]) + +// Stack +#define SET_SP( v ) (sp = ((v) + 1) | 0x100) +#define GET_SP() ((sp - 1) & 0xFF) +#define SP( o ) ((sp + (o - (o>0)*0x100)) | 0x100) + +// Truncation +#define BYTE( n ) ((uint8_t ) (n)) /* (unsigned) n & 0xFF */ +#define SBYTE( n ) ((int8_t ) (n)) /* (BYTE( n ) ^ 0x80) - 0x80 */ +#define WORD( n ) ((uint16_t) (n)) /* (unsigned) n & 0xFFFF */ + +// Flags with hex value for clarity when used as mask. +// Stored in indicated variable during emulation. +int const n80 = 0x80; // nz +int const v40 = 0x40; // flags +int const r20 = 0x20; +int const b10 = 0x10; +int const d08 = 0x08; // flags +int const i04 = 0x04; // flags +int const z02 = 0x02; // nz +int const c01 = 0x01; // c + +#define IS_NEG (nz & 0x8080) + +#define GET_FLAGS( out ) \ +{\ + out = flags & (v40 | d08 | i04);\ + out += ((nz >> 8) | nz) & n80;\ + out += c >> 8 & c01;\ + if ( !BYTE( nz ) )\ + out += z02;\ +} + +#define SET_FLAGS( in ) \ +{\ + flags = in & (v40 | d08 | i04);\ + c = nz = in << 8;\ + nz += ~in & z02;\ +} + +{ + int const time_offset = 0; + + // Local state + struct cpu_state_t s; + #ifdef FLAT_MEM + s.base = cpu->cpu_state_.base; + #else + s = cpu->cpu_state_; + #endif + cpu->cpu_state = &s; + int s_time = cpu->cpu_state_.time; // helps even on x86 + + // Registers + int pc = cpu->r.pc; + int a = cpu->r.a; + int x = cpu->r.x; + int y = cpu->r.y; + int sp; + SET_SP( cpu->r.sp ); + + // Flags + int flags; + int c; // carry set if (c & 0x100) != 0 + int nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x8080) != 0 + { + int temp = cpu->r.flags; + SET_FLAGS( temp ); + } + +loop: + + // Check all values + check( (unsigned) sp - 0x100 < 0x100 ); + check( (unsigned) pc < 0x10000 ); + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + check( (unsigned) y < 0x100 ); + + // Read instruction + byte const* instr = CODE_PAGE( pc ); + int opcode; + + if ( CODE_OFFSET(~0) == ~0 ) + { + opcode = instr [pc]; + pc++; + instr += pc; + } + else + { + instr += CODE_OFFSET( pc ); + opcode = *instr++; + pc++; + } + + // local to function in case it helps optimizer + static byte const clock_table [256] = + {// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,// 0 + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 1 + 6,6,0,8,3,3,5,5,4,2,2,2,4,4,6,6,// 2 + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 3 + 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,// 4 + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 5 + 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,// 6 + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 7 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// 8 + 2,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,// 9 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// A + 2,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4,// B + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// C + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// D + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// E + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7 // F + }; // 0x00 was 7 and 0x22 was 2 + + // Update time + if ( s_time >= 0 ) + goto out_of_time; + + #ifdef CPU_INSTR_HOOK + { CPU_INSTR_HOOK( (pc-1), (&instr [-1]), a, x, y, GET_SP(), TIME() ); } + #endif + + s_time += clock_table [opcode]; + + int data; + data = *instr; + + switch ( opcode ) + { + +// Macros + +#define GET_MSB() (instr [1]) +#define ADD_PAGE( out ) (pc++, out = data + 0x100 * GET_MSB()) +#define GET_ADDR() GET_LE16( instr ) + +#define PAGE_PENALTY( lsb ) s_time += (lsb) >> 8; + +#define INC_DEC( reg, n ) reg = BYTE( nz = reg + n ); goto loop; + +#define IND_Y( cross, out ) {\ + int temp = READ_LOW( data ) + y;\ + out = temp + 0x100 * READ_LOW( BYTE( data + 1 ) );\ + cross( temp );\ + } + +#define IND_X( out ) {\ + int temp = data + x;\ + out = 0x100 * READ_LOW( BYTE( temp + 1 ) ) + READ_LOW( BYTE( temp ) );\ + } + +#define ARITH_ADDR_MODES( op )\ +case op - 0x04: /* (ind,x) */\ + IND_X( data )\ + goto ptr##op;\ +case op + 0x0C: /* (ind),y */\ + IND_Y( PAGE_PENALTY, data )\ + goto ptr##op;\ +case op + 0x10: /* zp,X */\ + data = BYTE( data + x );\ +case op + 0x00: /* zp */\ + data = READ_LOW( data );\ + goto imm##op;\ +case op + 0x14: /* abs,Y */\ + data += y;\ + goto ind##op;\ +case op + 0x18: /* abs,X */\ + data += x;\ +ind##op:\ + PAGE_PENALTY( data );\ +case op + 0x08: /* abs */\ + ADD_PAGE( data );\ +ptr##op:\ + FLUSH_TIME();\ + data = READ_MEM( data );\ + CACHE_TIME();\ +case op + 0x04: /* imm */\ +imm##op: + +// TODO: more efficient way to handle negative branch that wraps PC around +#define BRANCH( cond )\ +{\ + ++pc;\ + if ( !(cond) ) goto loop;\ + s_time++;\ + int offset = SBYTE( data );\ + s_time += (BYTE(pc) + offset) >> 8 & 1;\ + pc = WORD( pc + offset );\ + goto loop;\ +} + +// Often-Used + + case 0xB5: // LDA zp,x + a = nz = READ_LOW( BYTE( data + x ) ); + pc++; + goto loop; + + case 0xA5: // LDA zp + a = nz = READ_LOW( data ); + pc++; + goto loop; + + case 0xD0: // BNE + BRANCH( BYTE( nz ) ); + + case 0x20: { // JSR + int temp = pc + 1; + pc = GET_ADDR(); + WRITE_STACK( SP( -1 ), temp >> 8 ); + sp = SP( -2 ); + WRITE_STACK( sp, temp ); + goto loop; + } + + case 0x4C: // JMP abs + pc = GET_ADDR(); + goto loop; + + case 0xE8: // INX + INC_DEC( x, 1 ) + + case 0x10: // BPL + BRANCH( !IS_NEG ) + + ARITH_ADDR_MODES( 0xC5 ) // CMP + nz = a - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0x30: // BMI + BRANCH( IS_NEG ) + + case 0xF0: // BEQ + BRANCH( !BYTE( nz ) ); + + case 0x95: // STA zp,x + data = BYTE( data + x ); + case 0x85: // STA zp + pc++; + WRITE_LOW( data, a ); + goto loop; + + case 0xC8: // INY + INC_DEC( y, 1 ) + + case 0xA8: // TAY + y = a; + nz = a; + goto loop; + + case 0x98: // TYA + a = y; + nz = y; + goto loop; + + case 0xAD:{// LDA abs + int addr = GET_ADDR(); + pc += 2; + READ_PPU( addr, a = nz ); + goto loop; + } + + case 0x60: // RTS + pc = 1 + READ_STACK( sp ); + pc += 0x100 * READ_STACK( SP( 1 ) ); + sp = SP( 2 ); + goto loop; + + { + int addr; + + case 0x8D: // STA abs + addr = GET_ADDR(); + pc += 2; + if ( CAN_WRITE_FAST( addr ) ) + { + WRITE_FAST( addr, a ); + goto loop; + } + sta_ptr: + FLUSH_TIME(); + WRITE_MEM( addr, a ); + CACHE_TIME(); + goto loop; + + case 0x99: // STA abs,Y + addr = y + GET_ADDR(); + pc += 2; + if ( CAN_WRITE_FAST( addr ) ) + { + WRITE_FAST( addr, a ); + goto loop; + } + goto sta_abs_x; + + case 0x9D: // STA abs,X (slightly more common than STA abs) + addr = x + GET_ADDR(); + pc += 2; + if ( CAN_WRITE_FAST( addr ) ) + { + WRITE_FAST( addr, a ); + goto loop; + } + DUMMY_READ( addr, x ); + sta_abs_x: + FLUSH_TIME(); + WRITE_MEM( addr, a ); + CACHE_TIME(); + goto loop; + + case 0x91: // STA (ind),Y + #define NO_PAGE_PENALTY( lsb ) + IND_Y( NO_PAGE_PENALTY, addr ) + pc++; + DUMMY_READ( addr, y ); + goto sta_ptr; + + case 0x81: // STA (ind,X) + IND_X( addr ) + pc++; + goto sta_ptr; + + } + + case 0xA9: // LDA #imm + pc++; + a = data; + nz = data; + goto loop; + + // common read instructions + { + int addr; + + case 0xA1: // LDA (ind,X) + IND_X( addr ) + pc++; + goto a_nz_read_addr; + + case 0xB1:// LDA (ind),Y + addr = READ_LOW( data ) + y; + PAGE_PENALTY( addr ); + addr += 0x100 * READ_LOW( BYTE( data + 1 ) ); + pc++; + READ_FAST( addr, a = nz ); + if ( CAN_READ_FAST( addr ) ) + goto loop; + DUMMY_READ( addr, y ); + goto a_nz_read_addr; + + case 0xB9: // LDA abs,Y + PAGE_PENALTY( data + y ); + addr = GET_ADDR() + y; + pc += 2; + READ_FAST( addr, a = nz ); + if ( CAN_READ_FAST( addr ) ) + goto loop; + goto a_nz_read_addr; + + case 0xBD: // LDA abs,X + PAGE_PENALTY( data + x ); + addr = GET_ADDR() + x; + pc += 2; + READ_FAST( addr, a = nz ); + if ( CAN_READ_FAST( addr ) ) + goto loop; + DUMMY_READ( addr, x ); + a_nz_read_addr: + FLUSH_TIME(); + a = nz = READ_MEM( addr ); + CACHE_TIME(); + goto loop; + + } + +// Branch + + case 0x50: // BVC + BRANCH( !(flags & v40) ) + + case 0x70: // BVS + BRANCH( flags & v40 ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + +// Load/store + + case 0x94: // STY zp,x + data = BYTE( data + x ); + case 0x84: // STY zp + pc++; + WRITE_LOW( data, y ); + goto loop; + + case 0x96: // STX zp,y + data = BYTE( data + y ); + case 0x86: // STX zp + pc++; + WRITE_LOW( data, x ); + goto loop; + + case 0xB6: // LDX zp,y + data = BYTE( data + y ); + case 0xA6: // LDX zp + data = READ_LOW( data ); + case 0xA2: // LDX #imm + pc++; + x = data; + nz = data; + goto loop; + + case 0xB4: // LDY zp,x + data = BYTE( data + x ); + case 0xA4: // LDY zp + data = READ_LOW( data ); + case 0xA0: // LDY #imm + pc++; + y = data; + nz = data; + goto loop; + + case 0xBC: // LDY abs,X + data += x; + PAGE_PENALTY( data ); + case 0xAC:{// LDY abs + int addr = data + 0x100 * GET_MSB(); + pc += 2; + FLUSH_TIME(); + y = nz = READ_MEM( addr ); + CACHE_TIME(); + goto loop; + } + + case 0xBE: // LDX abs,y + data += y; + PAGE_PENALTY( data ); + case 0xAE:{// LDX abs + int addr = data + 0x100 * GET_MSB(); + pc += 2; + FLUSH_TIME(); + x = nz = READ_MEM( addr ); + CACHE_TIME(); + goto loop; + } + + { + int temp; + case 0x8C: // STY abs + temp = y; + goto store_abs; + + case 0x8E: // STX abs + temp = x; + store_abs: + { + int addr = GET_ADDR(); + pc += 2; + if ( CAN_WRITE_FAST( addr ) ) + { + WRITE_FAST( addr, temp ); + goto loop; + } + FLUSH_TIME(); + WRITE_MEM( addr, temp ); + CACHE_TIME(); + goto loop; + } + } + +// Compare + + case 0xEC: {// CPX abs + int addr = GET_ADDR(); + pc++; + FLUSH_TIME(); + data = READ_MEM( addr ); + CACHE_TIME(); + goto cpx_data; + } + + case 0xE4: // CPX zp + data = READ_LOW( data ); + case 0xE0: // CPX #imm + cpx_data: + nz = x - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0xCC:{// CPY abs + int addr = GET_ADDR(); + pc++; + FLUSH_TIME(); + data = READ_MEM( addr ); + CACHE_TIME(); + goto cpy_data; + } + + case 0xC4: // CPY zp + data = READ_LOW( data ); + case 0xC0: // CPY #imm + cpy_data: + nz = y - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + +// Logical + + ARITH_ADDR_MODES( 0x25 ) // AND + nz = (a &= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x45 ) // EOR + nz = (a ^= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x05 ) // ORA + nz = (a |= data); + pc++; + goto loop; + + case 0x2C:{// BIT abs + int addr = GET_ADDR(); + pc += 2; + READ_PPU( addr, nz ); + flags = (flags & ~v40) + (nz & v40); + if ( a & nz ) + goto loop; + nz <<= 8; // result must be zero, even if N bit is set + goto loop; + } + + case 0x24: // BIT zp + nz = READ_LOW( data ); + pc++; + flags = (flags & ~v40) + (nz & v40); + if ( a & nz ) + goto loop; // Z should be clear, and nz must be non-zero if nz & a is + nz <<= 8; // set Z flag without affecting N flag + goto loop; + +// Add/subtract + + ARITH_ADDR_MODES( 0xE5 ) // SBC + case 0xEB: // unofficial equivalent + data ^= 0xFF; + goto adc_imm; + + ARITH_ADDR_MODES( 0x65 ) // ADC + adc_imm: { + int carry = c >> 8 & 1; + int ov = (a ^ 0x80) + carry + SBYTE( data ); + flags = (flags & ~v40) + (ov >> 2 & v40); + c = nz = a + data + carry; + pc++; + a = BYTE( nz ); + goto loop; + } + +// Shift/rotate + + case 0x4A: // LSR A + c = 0; + case 0x6A: // ROR A + nz = c >> 1 & 0x80; + c = a << 8; + nz += a >> 1; + a = nz; + goto loop; + + case 0x0A: // ASL A + nz = a << 1; + c = nz; + a = BYTE( nz ); + goto loop; + + case 0x2A: { // ROL A + nz = a << 1; + int temp = c >> 8 & 1; + c = nz; + nz += temp; + a = BYTE( nz ); + goto loop; + } + + case 0x5E: // LSR abs,X + data += x; + case 0x4E: // LSR abs + c = 0; + case 0x6E: // ROR abs + ror_abs: { + ADD_PAGE( data ); + FLUSH_TIME(); + int temp = READ_MEM( data ); + nz = (c >> 1 & 0x80) + (temp >> 1); + c = temp << 8; + goto rotate_common; + } + + case 0x3E: // ROL abs,X + data += x; + goto rol_abs; + + case 0x1E: // ASL abs,X + data += x; + case 0x0E: // ASL abs + c = 0; + case 0x2E: // ROL abs + rol_abs: + ADD_PAGE( data ); + nz = c >> 8 & 1; + FLUSH_TIME(); + nz += (c = READ_MEM( data ) << 1); + rotate_common: + pc++; + WRITE_MEM( data, BYTE( nz ) ); + CACHE_TIME(); + goto loop; + + case 0x7E: // ROR abs,X + data += x; + goto ror_abs; + + case 0x76: // ROR zp,x + data = BYTE( data + x ); + goto ror_zp; + + case 0x56: // LSR zp,x + data = BYTE( data + x ); + case 0x46: // LSR zp + c = 0; + case 0x66: // ROR zp + ror_zp: { + int temp = READ_LOW( data ); + nz = (c >> 1 & 0x80) + (temp >> 1); + c = temp << 8; + goto write_nz_zp; + } + + case 0x36: // ROL zp,x + data = BYTE( data + x ); + goto rol_zp; + + case 0x16: // ASL zp,x + data = BYTE( data + x ); + case 0x06: // ASL zp + c = 0; + case 0x26: // ROL zp + rol_zp: + nz = c >> 8 & 1; + nz += (c = READ_LOW( data ) << 1); + goto write_nz_zp; + +// Increment/decrement + + case 0xCA: // DEX + INC_DEC( x, -1 ) + + case 0x88: // DEY + INC_DEC( y, -1 ) + + case 0xF6: // INC zp,x + data = BYTE( data + x ); + case 0xE6: // INC zp + nz = 1; + goto add_nz_zp; + + case 0xD6: // DEC zp,x + data = BYTE( data + x ); + case 0xC6: // DEC zp + nz = -1; + add_nz_zp: + nz += READ_LOW( data ); + write_nz_zp: + pc++; + WRITE_LOW( data, nz ); + goto loop; + + case 0xFE: // INC abs,x + data = x + GET_ADDR(); + goto inc_ptr; + + case 0xEE: // INC abs + data = GET_ADDR(); + inc_ptr: + nz = 1; + goto inc_common; + + case 0xDE: // DEC abs,x + data = x + GET_ADDR(); + goto dec_ptr; + + case 0xCE: // DEC abs + data = GET_ADDR(); + dec_ptr: + nz = -1; + inc_common: + FLUSH_TIME(); + pc += 2; + nz += READ_MEM( data ); + WRITE_MEM( data, BYTE( nz ) ); + CACHE_TIME(); + goto loop; + +// Transfer + + case 0xAA: // TAX + x = nz = a; + goto loop; + + case 0x8A: // TXA + a = nz = x; + goto loop; + + case 0x9A: // TXS + SET_SP( x ); // verified (no flag change) + goto loop; + + case 0xBA: // TSX + x = nz = GET_SP(); + goto loop; + +// Stack + + case 0x48: // PHA + sp = SP( -1 ); + WRITE_STACK( sp, a ); + goto loop; + + case 0x68: // PLA + a = nz = READ_STACK( sp ); + sp = SP( 1 ); + goto loop; + + case 0x40:{// RTI + pc = READ_STACK( SP( 1 ) ); + pc += READ_STACK( SP( 2 ) ) * 0x100; + int temp = READ_STACK( sp ); + sp = SP( 3 ); + data = flags; + SET_FLAGS( temp ); + cpu->r.flags = flags; // update externally-visible I flag + int delta = s.base - cpu->irq_time; + if ( delta <= 0 ) goto loop; // end_time < irq_time + if ( flags & i04 ) goto loop; + s_time += delta; + s.base = cpu->irq_time; + goto loop; + } + + case 0x28:{// PLP + int temp = READ_STACK( sp ); + sp = SP( 1 ); + int changed = flags ^ temp; + SET_FLAGS( temp ); + if ( !(changed & i04) ) + goto loop; // I flag didn't change + if ( flags & i04 ) + goto handle_sei; + goto handle_cli; + } + + case 0x08:{// PHP + int temp; + GET_FLAGS( temp ); + sp = SP( -1 ); + WRITE_STACK( sp, temp | (b10 | r20) ); + goto loop; + } + + case 0x6C:{// JMP (ind) + data = GET_ADDR(); + byte const* page = CODE_PAGE( data ); + pc = page [CODE_OFFSET( data )]; + data = (data & 0xFF00) + ((data + 1) & 0xFF); + pc += page [CODE_OFFSET( data )] * 0x100; + goto loop; + } + + case 0x00: // BRK + goto handle_brk; + +// Flags + + case 0x38: // SEC + c = 0x100; + goto loop; + + case 0x18: // CLC + c = 0; + goto loop; + + case 0xB8: // CLV + flags &= ~v40; + goto loop; + + case 0xD8: // CLD + flags &= ~d08; + goto loop; + + case 0xF8: // SED + flags |= d08; + goto loop; + + case 0x58: // CLI + if ( !(flags & i04) ) + goto loop; + flags &= ~i04; + handle_cli: { + //dprintf( "CLI at %d\n", TIME ); + cpu->r.flags = flags; // update externally-visible I flag + int delta = s.base - cpu->irq_time; + if ( delta <= 0 ) + { + if ( TIME() < cpu->irq_time ) + goto loop; + goto delayed_cli; + } + s.base = cpu->irq_time; + s_time += delta; + if ( s_time < 0 ) + goto loop; + + if ( delta >= s_time + 1 ) + { + // delayed irq until after next instruction + s.base += s_time + 1; + s_time = -1; + goto loop; + } + + // TODO: implement + delayed_cli: + dprintf( "Delayed CLI not emulated\n" ); + goto loop; + } + + case 0x78: // SEI + if ( flags & i04 ) + goto loop; + flags |= i04; + handle_sei: { + cpu->r.flags = flags; // update externally-visible I flag + int delta = s.base - cpu->end_time; + s.base = cpu->end_time; + s_time += delta; + if ( s_time < 0 ) + goto loop; + + dprintf( "Delayed SEI not emulated\n" ); + goto loop; + } + +// Unofficial + + // SKW - skip word + case 0x1C: case 0x3C: case 0x5C: case 0x7C: case 0xDC: case 0xFC: + PAGE_PENALTY( data + x ); + case 0x0C: + pc++; + // SKB - skip byte + case 0x74: case 0x04: case 0x14: case 0x34: case 0x44: case 0x54: case 0x64: + case 0x80: case 0x82: case 0x89: case 0xC2: case 0xD4: case 0xE2: case 0xF4: + pc++; + goto loop; + + // NOP + case 0xEA: case 0x1A: case 0x3A: case 0x5A: case 0x7A: case 0xDA: case 0xFA: + goto loop; + + case halt_opcode: // HLT - halt processor + if ( pc-- > 0x10000 ) + { + // handle wrap-around (assumes caller has put page of HLT at 0x10000) + pc = WORD( pc ); + goto loop; + } + case 0x02: case 0x12: case 0x32: case 0x42: case 0x52: + case 0x62: case 0x72: case 0x92: case 0xB2: case 0xD2: case 0xF2: + goto stop; + +// Unimplemented + + case 0xFF: // force 256-entry jump table for optimization purposes + c |= 1; // compiler doesn't know that this won't affect anything + default: + check( (unsigned) opcode < 0x100 ); + + #ifdef UNIMPL_INSTR + UNIMPL_INSTR(); + #endif + + // At least skip over proper number of bytes instruction uses + static unsigned char const illop_lens [8] = { + 0x40, 0x40, 0x40, 0x80, 0x40, 0x40, 0x80, 0xA0 + }; + int opcode = instr [-1]; + int len = illop_lens [opcode >> 2 & 7] >> (opcode << 1 & 6) & 3; + if ( opcode == 0x9C ) + len = 2; + pc += len; + + // Account for extra clock + if ( (opcode >> 4) == 0x0B ) + { + if ( opcode == 0xB3 ) + data = READ_LOW( data ); + if ( opcode != 0xB7 ) + PAGE_PENALTY( data + y ); + } + goto loop; + } + assert( false ); // catch missing 'goto loop' or accidental 'break' + + int result_; +handle_brk: + pc++; + result_ = b10 | 4; + +#ifdef CPU_DONE +interrupt: +#endif + { + s_time += 7; + + // Save PC and read vector + WRITE_STACK( SP( -1 ), pc >> 8 ); + WRITE_STACK( SP( -2 ), pc ); + pc = GET_LE16( &READ_CODE( 0xFFFA ) + (result_ & 4) ); + + // Save flags + int temp; + GET_FLAGS( temp ); + temp |= r20 + (result_ & b10); // B flag set for BRK + sp = SP( -3 ); + WRITE_STACK( sp, temp ); + + // Update I flag in externally-visible flags + cpu->r.flags = (flags |= i04); + + // Update time + int delta = s.base - cpu->end_time; + if ( delta >= 0 ) + goto loop; + s_time += delta; + s.base = cpu->end_time; + goto loop; + } + +out_of_time: + pc--; + + // Optional action that triggers interrupt or changes irq/end time + #ifdef CPU_DONE + { + CPU_DONE( result_ ); + if ( result_ >= 0 ) + goto interrupt; + if ( s_time < 0 ) + goto loop; + } + #endif +stop: + + // Flush cached state + cpu->r.pc = pc; + cpu->r.sp = GET_SP(); + cpu->r.a = a; + cpu->r.x = x; + cpu->r.y = y; + + int temp; + GET_FLAGS( temp ); + cpu->r.flags = temp; + + cpu->cpu_state_.base = s.base; + cpu->cpu_state_.time = s_time; + cpu->cpu_state = &cpu->cpu_state_; +} diff --git a/apps/codecs/libgme/nes_fds_apu.c b/apps/codecs/libgme/nes_fds_apu.c new file mode 100644 index 0000000000..51421415e6 --- /dev/null +++ b/apps/codecs/libgme/nes_fds_apu.c @@ -0,0 +1,291 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "nes_fds_apu.h" + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const fract_range = 65536; + +void Fds_init( struct Nes_Fds_Apu* this ) +{ + Synth_init( &this->synth ); + + this->lfo_tempo = lfo_base_tempo; + Fds_set_output( this, 0, NULL ); + Fds_volume( this, 1.0 ); + Fds_reset( this ); +} + +void Fds_reset( struct Nes_Fds_Apu* this ) +{ + memset( this->regs_, 0, sizeof this->regs_ ); + memset( this->mod_wave, 0, sizeof this->mod_wave ); + + this->last_time = 0; + this->env_delay = 0; + this->sweep_delay = 0; + this->wave_pos = 0; + this->last_amp = 0; + this->wave_fract = fract_range; + this->mod_fract = fract_range; + this->mod_pos = 0; + this->mod_write_pos = 0; + + static byte const initial_regs [0x0B] ICONST_ATTR = { + 0x80, // disable envelope + 0, 0, 0xC0, // disable wave and lfo + 0x80, // disable sweep + 0, 0, 0x80, // disable modulation + 0, 0, 0xFF // LFO period // TODO: use 0xE8 as FDS ROM does? + }; + int i; + for ( i = 0; i < (int) sizeof initial_regs; i++ ) + { + // two writes to set both gain and period for envelope registers + Fds_write_( this, fds_io_addr + fds_wave_size + i, 0 ); + Fds_write_( this, fds_io_addr + fds_wave_size + i, initial_regs [i] ); + } +} + +void Fds_write_( struct Nes_Fds_Apu* this, unsigned addr, int data ) +{ + unsigned reg = addr - fds_io_addr; + if ( reg < fds_io_size ) + { + if ( reg < fds_wave_size ) + { + if ( *regs_nes (this, 0x4089) & 0x80 ) + this->regs_ [reg] = data & fds_wave_sample_max; + } + else + { + this->regs_ [reg] = data; + switch ( addr ) + { + case 0x4080: + if ( data & 0x80 ) + this->env_gain = data & 0x3F; + else + this->env_speed = (data & 0x3F) + 1; + break; + + case 0x4084: + if ( data & 0x80 ) + this->sweep_gain = data & 0x3F; + else + this->sweep_speed = (data & 0x3F) + 1; + break; + + case 0x4085: + this->mod_pos = this->mod_write_pos; + *regs_nes (this, 0x4085) = data & 0x7F; + break; + + case 0x4088: + if ( *regs_nes (this, 0x4087) & 0x80 ) + { + int pos = this->mod_write_pos; + data &= 0x07; + this->mod_wave [pos ] = data; + this->mod_wave [pos + 1] = data; + this->mod_write_pos = (pos + 2) & (fds_wave_size - 1); + this->mod_pos = (this->mod_pos + 2) & (fds_wave_size - 1); + } + break; + } + } + } +} + +void Fds_set_tempo( struct Nes_Fds_Apu* this, double t ) +{ + this->lfo_tempo = lfo_base_tempo; + if ( t != 1.0 ) + { + this->lfo_tempo = (int) ((double) lfo_base_tempo / t + 0.5); + if ( this->lfo_tempo <= 0 ) + this->lfo_tempo = 1; + } +} + +void Fds_run_until( struct Nes_Fds_Apu* this, blip_time_t final_end_time ) +{ + int const wave_freq = (*regs_nes (this, 0x4083) & 0x0F) * 0x100 + *regs_nes (this, 0x4082); + struct Blip_Buffer* const output_ = this->output_; + if ( wave_freq && output_ && !((*regs_nes (this, 0x4089) | *regs_nes (this, 0x4083)) & 0x80) ) + { + Blip_set_modified( output_ ); + + // master_volume + #define MVOL_ENTRY( percent ) (fds_master_vol_max * percent + 50) / 100 + static unsigned char const master_volumes [4] = { + MVOL_ENTRY( 100 ), MVOL_ENTRY( 67 ), MVOL_ENTRY( 50 ), MVOL_ENTRY( 40 ) + }; + int const master_volume = master_volumes [*regs_nes (this, 0x4089) & 0x03]; + + // lfo_period + blip_time_t lfo_period = *regs_nes (this, 0x408A) * this->lfo_tempo; + if ( *regs_nes (this, 0x4083) & 0x40 ) + lfo_period = 0; + + // sweep setup + blip_time_t sweep_time = this->last_time + this->sweep_delay; + blip_time_t const sweep_period = lfo_period * this->sweep_speed; + if ( !sweep_period || *regs_nes (this, 0x4084) & 0x80 ) + sweep_time = final_end_time; + + // envelope setup + blip_time_t env_time = this->last_time + this->env_delay; + blip_time_t const env_period = lfo_period * this->env_speed; + if ( !env_period || *regs_nes (this, 0x4080) & 0x80 ) + env_time = final_end_time; + + // modulation + int mod_freq = 0; + if ( !(*regs_nes (this, 0x4087) & 0x80) ) + mod_freq = (*regs_nes (this, 0x4087) & 0x0F) * 0x100 + *regs_nes (this, 0x4086); + + blip_time_t end_time = this->last_time; + do + { + // sweep + if ( sweep_time <= end_time ) + { + sweep_time += sweep_period; + int mode = *regs_nes (this, 0x4084) >> 5 & 2; + int new_sweep_gain = this->sweep_gain + mode - 1; + if ( (unsigned) new_sweep_gain <= (unsigned) 0x80 >> mode ) + this->sweep_gain = new_sweep_gain; + else + *regs_nes (this, 0x4084) |= 0x80; // optimization only + } + + // envelope + if ( env_time <= end_time ) + { + env_time += env_period; + int mode = *regs_nes (this, 0x4080) >> 5 & 2; + int new_env_gain = this->env_gain + mode - 1; + if ( (unsigned) new_env_gain <= (unsigned) 0x80 >> mode ) + this->env_gain = new_env_gain; + else + *regs_nes (this, 0x4080) |= 0x80; // optimization only + } + + // new end_time + blip_time_t const start_time = end_time; + end_time = final_end_time; + if ( end_time > env_time ) end_time = env_time; + if ( end_time > sweep_time ) end_time = sweep_time; + + // frequency modulation + int freq = wave_freq; + if ( mod_freq ) + { + // time of next modulation clock + blip_time_t mod_time = start_time + (this->mod_fract + mod_freq - 1) / mod_freq; + if ( end_time > mod_time ) + end_time = mod_time; + + // run modulator up to next clock and save old sweep_bias + int sweep_bias = *regs_nes (this, 0x4085); + this->mod_fract -= (end_time - start_time) * mod_freq; + if ( this->mod_fract <= 0 ) + { + this->mod_fract += fract_range; + check( (unsigned) this->mod_fract <= fract_range ); + + static short const mod_table [8] = { 0, +1, +2, +4, 0, -4, -2, -1 }; + int mod = this->mod_wave [this->mod_pos]; + this->mod_pos = (this->mod_pos + 1) & (fds_wave_size - 1); + int new_sweep_bias = (sweep_bias + mod_table [mod]) & 0x7F; + if ( mod == 4 ) + new_sweep_bias = 0; + *regs_nes (this, 0x4085) = new_sweep_bias; + } + + // apply frequency modulation + sweep_bias = (sweep_bias ^ 0x40) - 0x40; + int factor = sweep_bias * this->sweep_gain; + int extra = factor & 0x0F; + factor >>= 4; + if ( extra ) + { + factor--; + if ( sweep_bias >= 0 ) + factor += 3; + } + if ( factor > 193 ) factor -= 258; + if ( factor < -64 ) factor += 256; + freq += (freq * factor) >> 6; + if ( freq <= 0 ) + continue; + } + + // wave + int wave_fract = this->wave_fract; + blip_time_t delay = (wave_fract + freq - 1) / freq; + blip_time_t time = start_time + delay; + + if ( time <= end_time ) + { + // at least one wave clock within start_time...end_time + + blip_time_t const min_delay = fract_range / freq; + int wave_pos = this->wave_pos; + + int volume = this->env_gain; + if ( volume > fds_vol_max ) + volume = fds_vol_max; + volume *= master_volume; + + int const min_fract = min_delay * freq; + + do + { + // clock wave + int amp = this->regs_ [wave_pos] * volume; + wave_pos = (wave_pos + 1) & (fds_wave_size - 1); + int delta = amp - this->last_amp; + if ( delta ) + { + this->last_amp = amp; + Synth_offset_inline( &this->synth, time, delta, output_ ); + } + + wave_fract += fract_range - delay * freq; + check( unsigned (fract_range - wave_fract) < freq ); + + // delay until next clock + delay = min_delay; + if ( wave_fract > min_fract ) + delay++; + check( delay && delay == (wave_fract + freq - 1) / freq ); + + time += delay; + } + while ( time <= end_time ); // TODO: using < breaks things, but <= is wrong + + this->wave_pos = wave_pos; + } + this->wave_fract = wave_fract - (end_time - (time - delay)) * freq; + check( this->wave_fract > 0 ); + } + while ( end_time < final_end_time ); + + this->env_delay = env_time - final_end_time; check( env_delay >= 0 ); + this->sweep_delay = sweep_time - final_end_time; check( sweep_delay >= 0 ); + } + this->last_time = final_end_time; +} diff --git a/apps/codecs/libgme/nes_fds_apu.h b/apps/codecs/libgme/nes_fds_apu.h new file mode 100644 index 0000000000..1360e443db --- /dev/null +++ b/apps/codecs/libgme/nes_fds_apu.h @@ -0,0 +1,116 @@ +// NES FDS sound chip emulator + +// Game_Music_Emu 0.6-pre +#ifndef NES_FDS_APU_H +#define NES_FDS_APU_H + +#include "blargg_common.h" +#include "blip_buffer.h" + +enum { lfo_base_tempo = 8 }; +enum { fds_osc_count = 1 }; + +enum { fds_io_addr = 0x4040 }; +enum { fds_io_size = 0x53 }; + +enum { fds_wave_size = 0x40 }; +enum { fds_master_vol_max = 10 }; +enum { fds_vol_max = 0x20 }; +enum { fds_wave_sample_max = 0x3F }; + +struct Nes_Fds_Apu { + unsigned char regs_ [fds_io_size];// last written value to registers + int lfo_tempo; // normally 8; adjusted by set_tempo() + + int env_delay; + int env_speed; + int env_gain; + + int sweep_delay; + int sweep_speed; + int sweep_gain; + + int wave_pos; + int last_amp; + blip_time_t wave_fract; + + int mod_fract; + int mod_pos; + int mod_write_pos; + unsigned char mod_wave [fds_wave_size]; + + // synthesis + blip_time_t last_time; + struct Blip_Buffer* output_; + struct Blip_Synth synth; +}; + +// init +void Fds_init( struct Nes_Fds_Apu* this ); +// setup +void Fds_set_tempo( struct Nes_Fds_Apu* this, double t ); + +// emulation +void Fds_reset( struct Nes_Fds_Apu* this ); + +static inline void Fds_volume( struct Nes_Fds_Apu* this, double v ) +{ + Synth_volume( &this->synth, 0.14 / fds_master_vol_max / fds_vol_max / fds_wave_sample_max * v ); +} + +static inline void Fds_set_output( struct Nes_Fds_Apu* this, int i, struct Blip_Buffer* b ) +{ +#if defined(ROCKBOX) + (void) i; +#endif + + assert( (unsigned) i < fds_osc_count ); + this->output_ = b; +} + +void Fds_run_until( struct Nes_Fds_Apu* this, blip_time_t ) ICODE_ATTR; +static inline void Fds_end_frame( struct Nes_Fds_Apu* this, blip_time_t end_time ) +{ + if ( end_time > this->last_time ) + Fds_run_until( this, end_time ); + this->last_time -= end_time; + assert( this->last_time >= 0 ); +} + +void Fds_write_( struct Nes_Fds_Apu* this, unsigned addr, int data ) ICODE_ATTR; +static inline void Fds_write( struct Nes_Fds_Apu* this, blip_time_t time, unsigned addr, int data ) +{ + Fds_run_until( this, time ); + Fds_write_( this, addr, data ); +} + +static inline int Fds_read( struct Nes_Fds_Apu* this, blip_time_t time, unsigned addr ) +{ + Fds_run_until( this, time ); + + int result = 0xFF; + switch ( addr ) + { + case 0x4090: + result = this->env_gain; + break; + + case 0x4092: + result = this->sweep_gain; + break; + + default: + { + unsigned i = addr - fds_io_addr; + if ( i < fds_wave_size ) + result = this->regs_ [i]; + } + } + + return result | 0x40; +} + +// allow access to registers by absolute address (i.e. 0x4080) +static inline unsigned char* regs_nes( struct Nes_Fds_Apu* this, unsigned addr ) { return &this->regs_ [addr - fds_io_addr]; } + +#endif diff --git a/apps/codecs/libgme/nes_fme7_apu.c b/apps/codecs/libgme/nes_fme7_apu.c new file mode 100644 index 0000000000..cb5ed93be5 --- /dev/null +++ b/apps/codecs/libgme/nes_fme7_apu.c @@ -0,0 +1,135 @@ +// Game_Music_Emu 0.5.5. http://www.slack.net/~ant/ + +#include "nes_fme7_apu.h" + +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void Fme7_init( struct Nes_Fme7_Apu* this ) +{ + Synth_init( &this->synth ); + + Fme7_output( this, NULL ); + Fme7_volume( this, 1.0 ); + Fme7_reset( this ); +} + +void Fme7_reset( struct Nes_Fme7_Apu* this ) +{ + this->last_time = 0; + + int i; + for ( i = 0; i < fme7_osc_count; i++ ) + this->oscs [i].last_amp = 0; + + this->latch = 0; + memset( this->regs, 0, sizeof this->regs); + memset( this->phases, 0, sizeof this->phases ); + memset( this->delays, 0, sizeof this->delays ); +} + +static unsigned char const amp_table [16] ICONST_ATTR = +{ + #define ENTRY( n ) (unsigned char) (n * amp_range + 0.5) + ENTRY(0.0000), ENTRY(0.0078), ENTRY(0.0110), ENTRY(0.0156), + ENTRY(0.0221), ENTRY(0.0312), ENTRY(0.0441), ENTRY(0.0624), + ENTRY(0.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498), + ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000) + #undef ENTRY +}; + +void Fme7_run_until( struct Nes_Fme7_Apu* this, blip_time_t end_time ) +{ + require( end_time >= this->last_time ); + + int index; + for ( index = 0; index < fme7_osc_count; index++ ) + { + int mode = this->regs [7] >> index; + int vol_mode = this->regs [010 + index]; + int volume = amp_table [vol_mode & 0x0F]; + + struct Blip_Buffer* const osc_output = this->oscs [index].output; + if ( !osc_output ) + continue; + /* osc_output->set_modified(); */ + Blip_set_modified( osc_output ); + + // check for unsupported mode + #ifndef NDEBUG + if ( (mode & 011) <= 001 && vol_mode & 0x1F ) + debug_printf( "FME7 used unimplemented sound mode: %02X, vol_mode: %02X\n", + mode, vol_mode & 0x1F ); + #endif + + if ( (mode & 001) | (vol_mode & 0x10) ) + volume = 0; // noise and envelope aren't supported + + // period + int const period_factor = 16; + unsigned period = (this->regs [index * 2 + 1] & 0x0F) * 0x100 * period_factor + + this->regs [index * 2] * period_factor; + if ( period < 50 ) // around 22 kHz + { + volume = 0; + if ( !period ) // on my AY-3-8910A, period doesn't have extra one added + period = period_factor; + } + + // current amplitude + int amp = volume; + if ( !this->phases [index] ) + amp = 0; + { + int delta = amp - this->oscs [index].last_amp; + if ( delta ) + { + this->oscs [index].last_amp = amp; + Synth_offset( &this->synth, this->last_time, delta, osc_output ); + } + } + + blip_time_t time = this->last_time + this->delays [index]; + if ( time < end_time ) + { + int delta = amp * 2 - volume; + if ( volume ) + { + do + { + delta = -delta; + Synth_offset_inline( &this->synth, time, delta, osc_output ); + time += period; + } + while ( time < end_time ); + + this->oscs [index].last_amp = (delta + volume) >> 1; + this->phases [index] = (delta > 0); + } + else + { + // maintain phase when silent + int count = (end_time - time + period - 1) / period; + this->phases [index] ^= count & 1; + time += (blargg_long) count * period; + } + } + + this->delays [index] = time - end_time; + } + + this->last_time = end_time; +} + diff --git a/apps/codecs/libgme/nes_fme7_apu.h b/apps/codecs/libgme/nes_fme7_apu.h new file mode 100644 index 0000000000..47b93e5cfe --- /dev/null +++ b/apps/codecs/libgme/nes_fme7_apu.h @@ -0,0 +1,90 @@ +// Sunsoft FME-7 sound emulator + +// Game_Music_Emu 0.5.5 +#ifndef NES_FME7_APU_H +#define NES_FME7_APU_H + +#include "blargg_common.h" +#include "blip_buffer.h" + +enum { fme7_reg_count = 14 }; + +// Mask and addresses of registers +enum { fme7_addr_mask = 0xE000 }; +enum { fme7_data_addr = 0xE000 }; +enum { fme7_latch_addr = 0xC000 }; +enum { fme7_osc_count = 3 }; + +enum { amp_range = 192 }; // can be any value; this gives best error/quality tradeoff + +struct osc_t { + struct Blip_Buffer* output; + int last_amp; +}; + +// static unsigned char const amp_table [16]; + +struct Nes_Fme7_Apu { + // fme7 apu state + uint8_t regs [fme7_reg_count]; + uint8_t phases [3]; // 0 or 1 + uint8_t latch; + uint16_t delays [3]; // a, b, c + + struct osc_t oscs [fme7_osc_count]; + blip_time_t last_time; + + struct Blip_Synth synth; +}; + +// See Nes_Apu.h for reference +void Fme7_init( struct Nes_Fme7_Apu* this ); +void Fme7_reset( struct Nes_Fme7_Apu* this ); + +static inline void Fme7_volume( struct Nes_Fme7_Apu* this, double v ) +{ + Synth_volume( &this->synth, 0.38 / amp_range * v ); // to do: fine-tune +} + +static inline void Fme7_osc_output( struct Nes_Fme7_Apu* this, int i, struct Blip_Buffer* buf ) +{ + assert( (unsigned) i < fme7_osc_count ); + this->oscs [i].output = buf; +} + +static inline void Fme7_output( struct Nes_Fme7_Apu* this, struct Blip_Buffer* buf ) +{ + int i; + for ( i = 0; i < fme7_osc_count; i++ ) + Fme7_osc_output( this, i, buf ); +} + +// (addr & addr_mask) == latch_addr +static inline void Fme7_write_latch( struct Nes_Fme7_Apu* this, int data ) { this->latch = data; } + +// (addr & addr_mask) == data_addr +void Fme7_run_until( struct Nes_Fme7_Apu* this, blip_time_t end_time ) ICODE_ATTR; +static inline void Fme7_write_data( struct Nes_Fme7_Apu* this, blip_time_t time, int data ) +{ + if ( (unsigned) this->latch >= fme7_reg_count ) + { + #ifdef debug_printf + debug_printf( "FME7 write to %02X (past end of sound registers)\n", (int) latch ); + #endif + return; + } + + Fme7_run_until( this, time ); + this->regs [this->latch] = data; +} + +static inline void Fme7_end_frame( struct Nes_Fme7_Apu* this, blip_time_t time ) +{ + if ( time > this->last_time ) + Fme7_run_until( this, time ); + + assert( this->last_time >= time ); + this->last_time -= time; +} + +#endif diff --git a/apps/codecs/libgme/nes_mmc5_apu.h b/apps/codecs/libgme/nes_mmc5_apu.h new file mode 100644 index 0000000000..b696b49e97 --- /dev/null +++ b/apps/codecs/libgme/nes_mmc5_apu.h @@ -0,0 +1,61 @@ +// NES MMC5 sound chip emulator + +// Nes_Snd_Emu 0.2.0-pre +#ifndef NES_MMC5_APU_H +#define NES_MMC5_APU_H + +#include "blargg_common.h" +#include "nes_apu.h" + +enum { mmc5_regs_addr = 0x5000 }; +enum { mmc5_regs_size = 0x16 }; +enum { mmc5_osc_count = 3 }; +enum { mmc5_exram_size = 1024 }; + +struct Nes_Mmc5_Apu { + struct Nes_Apu apu; + unsigned char exram [mmc5_exram_size]; +}; + +static inline void Mmc5_init( struct Nes_Mmc5_Apu* this ) +{ + Apu_init( &this->apu ); +} + +static inline void Mmc5_set_output( struct Nes_Mmc5_Apu* this, int i, struct Blip_Buffer* b ) +{ + // in: square 1, square 2, PCM + // out: square 1, square 2, skipped, skipped, PCM + if ( i > 1 ) + i += 2; + Apu_osc_output( &this->apu, i, b ); +} + +static inline void Mmc5_write_register( struct Nes_Mmc5_Apu* this, blip_time_t time, unsigned addr, int data ) +{ + switch ( addr ) + { + case 0x5015: // channel enables + data &= 0x03; // enable the square waves only + // fall through + case 0x5000: // Square 1 + case 0x5002: + case 0x5003: + case 0x5004: // Square 2 + case 0x5006: + case 0x5007: + case 0x5011: // DAC + Apu_write_register( &this->apu, time, addr - 0x1000, data ); + break; + + case 0x5010: // some things write to this for some reason + break; + +#ifdef BLARGG_DEBUG_H + default: + dprintf( "Unmapped MMC5 APU write: $%04X <- $%02X\n", addr, data ); +#endif + } +} + +#endif diff --git a/apps/codecs/libgme/nes_namco_apu.c b/apps/codecs/libgme/nes_namco_apu.c new file mode 100644 index 0000000000..0fca501eff --- /dev/null +++ b/apps/codecs/libgme/nes_namco_apu.c @@ -0,0 +1,133 @@ +// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/ + +#include "nes_namco_apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void Namco_init( struct Nes_Namco_Apu* this ) +{ + Synth_init( &this->synth ); + + Namco_output( this, NULL ); + Namco_volume( this, 1.0 ); + Namco_reset( this ); +} + +void Namco_reset( struct Nes_Namco_Apu* this ) +{ + this->last_time = 0; + this->addr_reg = 0; + + int i; + for ( i = 0; i < namco_reg_count; i++ ) + this->reg [i] = 0; + + for ( i = 0; i < namco_osc_count; i++ ) + { + struct Namco_Osc* osc = &this->oscs [i]; + osc->delay = 0; + osc->last_amp = 0; + osc->wave_pos = 0; + } +} + +void Namco_output( struct Nes_Namco_Apu* this, struct Blip_Buffer* buf ) +{ + int i; + for ( i = 0; i < namco_osc_count; i++ ) + Namco_osc_output( this, i, buf ); +} + +void Namco_end_frame( struct Nes_Namco_Apu* this, blip_time_t time ) +{ + if ( time > this->last_time ) + Namco_run_until( this, time ); + + assert( this->last_time >= time ); + this->last_time -= time; +} + +void Namco_run_until( struct Nes_Namco_Apu* this, blip_time_t nes_end_time ) +{ + int active_oscs = (this->reg [0x7F] >> 4 & 7) + 1; + int i; + for ( i = namco_osc_count - active_oscs; i < namco_osc_count; i++ ) + { + struct Namco_Osc* osc = &this->oscs [i]; + struct Blip_Buffer* output = osc->output; + if ( !output ) + continue; + /* output->set_modified(); */ + Blip_set_modified( output ); + + blip_resampled_time_t time = + Blip_resampled_time( output, this->last_time ) + osc->delay; + blip_resampled_time_t end_time = Blip_resampled_time( output, nes_end_time ); + osc->delay = 0; + if ( time < end_time ) + { + const uint8_t* osc_reg = &this->reg [i * 8 + 0x40]; + if ( !(osc_reg [4] & 0xE0) ) + continue; + + int volume = osc_reg [7] & 15; + if ( !volume ) + continue; + + blargg_long freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100L + osc_reg [0]; + if ( freq < 64 * active_oscs ) + continue; // prevent low frequencies from excessively delaying freq changes + blip_resampled_time_t period = + /* output->resampled_duration( 983040 ) / freq * active_oscs; */ + Blip_resampled_duration( output, 983040 ) / freq * active_oscs; + + int wave_size = 32 - (osc_reg [4] >> 2 & 7) * 4; + if ( !wave_size ) + continue; + + int last_amp = osc->last_amp; + int wave_pos = osc->wave_pos; + + do + { + // read wave sample + int addr = wave_pos + osc_reg [6]; + int sample = this->reg [addr >> 1] >> (addr << 2 & 4); + wave_pos++; + sample = (sample & 15) * volume; + + // output impulse if amplitude changed + int delta = sample - last_amp; + if ( delta ) + { + last_amp = sample; + Synth_offset_resampled( &this->synth, time, delta, output ); + } + + // next sample + time += period; + if ( wave_pos >= wave_size ) + wave_pos = 0; + } + while ( time < end_time ); + + osc->wave_pos = wave_pos; + osc->last_amp = last_amp; + } + osc->delay = time - end_time; + } + + this->last_time = nes_end_time; +} + diff --git a/apps/codecs/libgme/nes_namco_apu.h b/apps/codecs/libgme/nes_namco_apu.h new file mode 100644 index 0000000000..b53b69808b --- /dev/null +++ b/apps/codecs/libgme/nes_namco_apu.h @@ -0,0 +1,71 @@ +// Namco 106 sound chip emulator + +// Nes_Snd_Emu 0.1.8 +#ifndef NES_NAMCO_APU_H +#define NES_NAMCO_APU_H + +#include "blargg_common.h" +#include "blip_buffer.h" + +struct namco_state_t; + +enum { namco_osc_count = 8 }; +enum { namco_addr_reg_addr = 0xF800 }; +enum { namco_data_reg_addr = 0x4800 }; +enum { namco_reg_count = 0x80 }; + +struct Namco_Osc { + blargg_long delay; + struct Blip_Buffer* output; + short last_amp; + short wave_pos; +}; + +struct Nes_Namco_Apu { + struct Namco_Osc oscs [namco_osc_count]; + + blip_time_t last_time; + int addr_reg; + + uint8_t reg [namco_reg_count]; + + struct Blip_Synth synth; +}; + +// See Nes_Apu.h for reference. +void Namco_init( struct Nes_Namco_Apu* this ); +void Namco_output( struct Nes_Namco_Apu* this, struct Blip_Buffer* ); + +void Namco_reset( struct Nes_Namco_Apu* this ); +void Namco_end_frame( struct Nes_Namco_Apu* this, blip_time_t ) ICODE_ATTR; + +static inline uint8_t* namco_access( struct Nes_Namco_Apu* this ) +{ + int addr = this->addr_reg & 0x7F; + if ( this->addr_reg & 0x80 ) + this->addr_reg = (addr + 1) | 0x80; + return &this->reg [addr]; +} + +static inline void Namco_volume( struct Nes_Namco_Apu* this, double v ) { Synth_volume( &this->synth, 0.10 / namco_osc_count * v / 15.0 ); } + +// Write-only address register is at 0xF800 +static inline void Namco_write_addr( struct Nes_Namco_Apu* this, int v ) { this->addr_reg = v; } + +static inline int Namco_read_data( struct Nes_Namco_Apu* this ) { return *namco_access( this ); } + +static inline void Namco_osc_output( struct Nes_Namco_Apu* this, int i, struct Blip_Buffer* buf ) +{ + assert( (unsigned) i < namco_osc_count ); + this->oscs [i].output = buf; +} + +// Read/write data register is at 0x4800 +void Namco_run_until( struct Nes_Namco_Apu* this, blip_time_t ) ICODE_ATTR; +static inline void Namco_write_data( struct Nes_Namco_Apu* this, blip_time_t time, int data ) +{ + Namco_run_until( this, time ); + *namco_access( this ) = data; +} + +#endif diff --git a/apps/codecs/libgme/nes_oscs.c b/apps/codecs/libgme/nes_oscs.c new file mode 100644 index 0000000000..f04d5fa9ad --- /dev/null +++ b/apps/codecs/libgme/nes_oscs.c @@ -0,0 +1,583 @@ +// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/ + +#include "nes_apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// Nes_Osc + +void Osc_clock_length( struct Nes_Osc* this, int halt_mask ) +{ + if ( this->length_counter && !(this->regs [0] & halt_mask) ) + this->length_counter--; +} + +// Nes_Square + +void Square_clock_envelope( struct Nes_Square* this ) +{ + struct Nes_Osc* osc = &this->osc; + int period = osc->regs [0] & 15; + if ( osc->reg_written [3] ) { + osc->reg_written [3] = false; + this->env_delay = period; + this->envelope = 15; + } + else if ( --this->env_delay < 0 ) { + this->env_delay = period; + if ( this->envelope | (osc->regs [0] & 0x20) ) + this->envelope = (this->envelope - 1) & 15; + } +} + +int Square_volume( struct Nes_Square* this ) +{ + struct Nes_Osc* osc = &this->osc; + return osc->length_counter == 0 ? 0 : (osc->regs [0] & 0x10) ? (osc->regs [0] & 15) : this->envelope; +} + +void Square_clock_sweep( struct Nes_Square* this, int negative_adjust ) +{ + struct Nes_Osc* osc = &this->osc; + int sweep = osc->regs [1]; + + if ( --this->sweep_delay < 0 ) + { + osc->reg_written [1] = true; + + int period = Osc_period( osc ); + int shift = sweep & shift_mask; + if ( shift && (sweep & 0x80) && period >= 8 ) + { + int offset = period >> shift; + + if ( sweep & negate_flag ) + offset = negative_adjust - offset; + + if ( period + offset < 0x800 ) + { + period += offset; + // rewrite period + osc->regs [2] = period & 0xFF; + osc->regs [3] = (osc->regs [3] & ~7) | ((period >> 8) & 7); + } + } + } + + if ( osc->reg_written [1] ) { + osc->reg_written [1] = false; + this->sweep_delay = (sweep >> 4) & 7; + } +} + +// TODO: clean up +inline nes_time_t Square_maintain_phase( struct Nes_Square* this, nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ) +{ + nes_time_t remain = end_time - time; + if ( remain > 0 ) + { + int count = (remain + timer_period - 1) / timer_period; + this->phase = (this->phase + count) & (square_phase_range - 1); + time += (blargg_long) count * timer_period; + } + return time; +} + +void Square_run( struct Nes_Square* this, nes_time_t time, nes_time_t end_time ) +{ + struct Nes_Osc* osc = &this->osc; + const int period = Osc_period( osc ); + const int timer_period = (period + 1) * 2; + + if ( !osc->output ) + { + osc->delay = Square_maintain_phase( this, time + osc->delay, end_time, timer_period ) - end_time; + return; + } + + Blip_set_modified( osc->output ); + + int offset = period >> (osc->regs [1] & shift_mask); + if ( osc->regs [1] & negate_flag ) + offset = 0; + + const int volume = Square_volume( this ); + if ( volume == 0 || period < 8 || (period + offset) >= 0x800 ) + { + if ( osc->last_amp ) { + Synth_offset( this->synth, time, -osc->last_amp, osc->output ); + osc->last_amp = 0; + } + + time += osc->delay; + time = Square_maintain_phase( this, time, end_time, timer_period ); + } + else + { + // handle duty select + int duty_select = (osc->regs [0] >> 6) & 3; + int duty = 1 << duty_select; // 1, 2, 4, 2 + int amp = 0; + if ( duty_select == 3 ) { + duty = 2; // negated 25% + amp = volume; + } + if ( this->phase < duty ) + amp ^= volume; + + { + int delta = Osc_update_amp( osc, amp ); + if ( delta ) + Synth_offset( this->synth, time, delta, osc->output ); + } + + time += osc->delay; + if ( time < end_time ) + { + struct Blip_Buffer* const output = osc->output; + Synth* synth = this->synth; + int delta = amp * 2 - volume; + int phase = this->phase; + + do { + phase = (phase + 1) & (square_phase_range - 1); + if ( phase == 0 || phase == duty ) { + delta = -delta; + Synth_offset_inline( synth, time, delta, output ); + } + time += timer_period; + } + while ( time < end_time ); + + osc->last_amp = (delta + volume) >> 1; + this->phase = phase; + } + } + + osc->delay = time - end_time; +} + +// Nes_Triangle + +void Triangle_clock_linear_counter( struct Nes_Triangle* this ) +{ + struct Nes_Osc* osc = &this->osc; + if ( osc->reg_written [3] ) + this->linear_counter = osc->regs [0] & 0x7F; + else if ( this->linear_counter ) + this->linear_counter--; + + if ( !(osc->regs [0] & 0x80) ) + osc->reg_written [3] = false; +} + +inline int Triangle_calc_amp( struct Nes_Triangle* this ) +{ + int amp = Triangle_phase_range - this->phase; + if ( amp < 0 ) + amp = this->phase - (Triangle_phase_range + 1); + return amp; +} + +// TODO: clean up +inline nes_time_t Triangle_maintain_phase( struct Nes_Triangle* this, nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ) +{ + nes_time_t remain = end_time - time; + if ( remain > 0 ) + { + int count = (remain + timer_period - 1) / timer_period; + this->phase = ((unsigned) this->phase + 1 - count) & (Triangle_phase_range * 2 - 1); + this->phase++; + time += (blargg_long) count * timer_period; + } + return time; +} + +void Triangle_run( struct Nes_Triangle* this, nes_time_t time, nes_time_t end_time ) +{ + struct Nes_Osc* osc = &this->osc; + const int timer_period = Osc_period( osc ) + 1; + if ( !osc->output ) + { + time += osc->delay; + osc->delay = 0; + if ( osc->length_counter && this->linear_counter && timer_period >= 3 ) + osc->delay = Triangle_maintain_phase( this, time, end_time, timer_period ) - end_time; + return; + } + + Blip_set_modified( osc->output ); + + // to do: track phase when period < 3 + // to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks. + + int delta = Osc_update_amp( osc, Triangle_calc_amp( this ) ); + if ( delta ) + Synth_offset( &this->synth, time, delta, osc->output ); + + time += osc->delay; + if ( osc->length_counter == 0 || this->linear_counter == 0 || timer_period < 3 ) + { + time = end_time; + } + else if ( time < end_time ) + { + struct Blip_Buffer* const output = osc->output; + + int phase = this->phase; + int volume = 1; + if ( phase > Triangle_phase_range ) { + phase -= Triangle_phase_range; + volume = -volume; + } + + do { + if ( --phase == 0 ) { + phase = Triangle_phase_range; + volume = -volume; + } + else { + Synth_offset_inline( &this->synth, time, volume, output ); + } + + time += timer_period; + } + while ( time < end_time ); + + if ( volume < 0 ) + phase += Triangle_phase_range; + this->phase = phase; + osc->last_amp = Triangle_calc_amp( this ); + } + osc->delay = time - end_time; +} + +// Nes_Dmc + +void Dmc_reset( struct Nes_Dmc* this ) +{ + this->address = 0; + this->dac = 0; + this->buf = 0; + this->bits_remain = 1; + this->bits = 0; + this->buf_full = false; + this->silence = true; + this->next_irq = apu_no_irq; + this->irq_flag = false; + this->irq_enabled = false; + + Osc_reset( &this->osc ); + this->period = 0x1AC; +} + +void Dmc_recalc_irq( struct Nes_Dmc* this ) +{ + struct Nes_Osc* osc = &this->osc; + nes_time_t irq = apu_no_irq; + if ( this->irq_enabled && osc->length_counter ) + irq = this->apu->last_dmc_time + osc->delay + + ((osc->length_counter - 1) * 8 + this->bits_remain - 1) * (nes_time_t) (this->period) + 1; + if ( irq != this->next_irq ) { + this->next_irq = irq; + Apu_irq_changed( this->apu ); + } +} + +int Dmc_count_reads( struct Nes_Dmc* this, nes_time_t time, nes_time_t* last_read ) +{ + struct Nes_Osc* osc = &this->osc; + if ( last_read ) + *last_read = time; + + if ( osc->length_counter == 0 ) + return 0; // not reading + + nes_time_t first_read = Dmc_next_read_time( this ); + nes_time_t avail = time - first_read; + if ( avail <= 0 ) + return 0; + + int count = (avail - 1) / (this->period * 8) + 1; + if ( !(osc->regs [0] & loop_flag) && count > osc->length_counter ) + count = osc->length_counter; + + if ( last_read ) + { + *last_read = first_read + (count - 1) * (this->period * 8) + 1; + check( *last_read <= time ); + check( count == count_reads( *last_read, NULL ) ); + check( count - 1 == count_reads( *last_read - 1, NULL ) ); + } + + return count; +} + +static short const dmc_period_table [2] [16] ICONST_ATTR = { + {428, 380, 340, 320, 286, 254, 226, 214, // NTSC + 190, 160, 142, 128, 106, 84, 72, 54}, + + {398, 354, 316, 298, 276, 236, 210, 198, // PAL + 176, 148, 132, 118, 98, 78, 66, 50} +}; + +inline void Dmc_reload_sample( struct Nes_Dmc* this ) +{ + this->address = 0x4000 + this->osc.regs [2] * 0x40; + this->osc.length_counter = this->osc.regs [3] * 0x10 + 1; +} + +static byte const dac_table [128] ICONST_ATTR = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9,10,11,12,13,14, + 15,15,16,17,18,19,20,20,21,22,23,24,24,25,26,27, + 27,28,29,30,31,31,32,33,33,34,35,36,36,37,38,38, + 39,40,41,41,42,43,43,44,45,45,46,47,47,48,48,49, + 50,50,51,52,52,53,53,54,55,55,56,56,57,58,58,59, + 59,60,60,61,61,62,63,63,64,64,65,65,66,66,67,67, + 68,68,69,70,70,71,71,72,72,73,73,74,74,75,75,75, + 76,76,77,77,78,78,79,79,80,80,81,81,82,82,82,83, +}; + +void Dmc_write_register( struct Nes_Dmc* this, int addr, int data ) +{ + if ( addr == 0 ) + { + this->period = dmc_period_table [this->pal_mode] [data & 15]; + this->irq_enabled = (data & 0xC0) == 0x80; // enabled only if loop disabled + this->irq_flag &= this->irq_enabled; + Dmc_recalc_irq( this ); + } + else if ( addr == 1 ) + { + int old_dac = this->dac; + this->dac = data & 0x7F; + + // adjust last_amp so that "pop" amplitude will be properly non-linear + // with respect to change in dac + int faked_nonlinear = this->dac - (dac_table [this->dac] - dac_table [old_dac]); + if ( !this->nonlinear ) + this->osc.last_amp = faked_nonlinear; + } +} + +void Dmc_start( struct Nes_Dmc* this ) +{ + Dmc_reload_sample( this ); + Dmc_fill_buffer( this ); + Dmc_recalc_irq( this ); +} + +void Dmc_fill_buffer( struct Nes_Dmc* this ) +{ + if ( !this->buf_full && this->osc.length_counter ) + { + require( this->prg_reader ); // prg_reader must be set + this->buf = this->prg_reader( this->prg_reader_data, 0x8000u + this->address ); + this->address = (this->address + 1) & 0x7FFF; + this->buf_full = true; + if ( --this->osc.length_counter == 0 ) + { + if ( this->osc.regs [0] & loop_flag ) { + Dmc_reload_sample( this ); + } + else { + this->apu->osc_enables &= ~0x10; + this->irq_flag = this->irq_enabled; + this->next_irq = apu_no_irq; + Apu_irq_changed( this->apu ); + } + } + } +} + +void Dmc_run( struct Nes_Dmc* this, nes_time_t time, nes_time_t end_time ) +{ + struct Nes_Osc* osc = &this->osc; + int delta = Osc_update_amp( osc, this->dac ); + if ( !osc->output ) + { + this->silence = true; + } + else + { + Blip_set_modified( osc->output ); + if ( delta ) + Synth_offset( &this->synth, time, delta, osc->output ); + } + + time += osc->delay; + if ( time < end_time ) + { + int bits_remain = this->bits_remain; + if ( this->silence && !this->buf_full ) + { + int count = (end_time - time + this->period - 1) / this->period; + bits_remain = (bits_remain - 1 + 8 - (count % 8)) % 8 + 1; + time += count * this->period; + } + else + { + struct Blip_Buffer* const output = osc->output; + const int period = this->period; + int bits = this->bits; + int dac = this->dac; + + do + { + if ( !this->silence ) + { + int step = (bits & 1) * 4 - 2; + bits >>= 1; + if ( (unsigned) (dac + step) <= 0x7F ) { + dac += step; + Synth_offset_inline( &this->synth, time, step, output ); + } + } + + time += period; + + if ( --bits_remain == 0 ) + { + bits_remain = 8; + if ( !this->buf_full ) { + this->silence = true; + } + else { + this->silence = false; + bits = this->buf; + this->buf_full = false; + if ( !output ) + this->silence = true; + Dmc_fill_buffer( this ); + } + } + } + while ( time < end_time ); + + this->dac = dac; + osc->last_amp = dac; + this->bits = bits; + } + this->bits_remain = bits_remain; + } + osc->delay = time - end_time; +} + +// Nes_Noise + +static short const noise_period_table [16] ICONST_ATTR = { + 0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0, + 0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4 +}; + +void Noise_clock_envelope( struct Nes_Noise* this ) +{ + struct Nes_Osc* osc = &this->osc; + int period = osc->regs [0] & 15; + if ( osc->reg_written [3] ) { + osc->reg_written [3] = false; + this->env_delay = period; + this->envelope = 15; + } + else if ( --this->env_delay < 0 ) { + this->env_delay = period; + if ( this->envelope | (osc->regs [0] & 0x20) ) + this->envelope = (this->envelope - 1) & 15; + } +} + +int Noise_volume( struct Nes_Noise* this ) +{ + struct Nes_Osc* osc = &this->osc; + return osc->length_counter == 0 ? 0 : (osc->regs [0] & 0x10) ? (osc->regs [0] & 15) : this->envelope; +} + +void Noise_run( struct Nes_Noise* this, nes_time_t time, nes_time_t end_time ) +{ + struct Nes_Osc* osc = &this->osc; + int period = noise_period_table [osc->regs [2] & 15]; + + if ( !osc->output ) + { + // TODO: clean up + time += osc->delay; + osc->delay = time + (end_time - time + period - 1) / period * period - end_time; + return; + } + + Blip_set_modified( osc->output ); + + const int volume = Noise_volume( this ); + int amp = (this->noise & 1) ? volume : 0; + { + int delta = Osc_update_amp( osc, amp ); + if ( delta ) + Synth_offset( &this->synth, time, delta, osc->output ); + } + + time += osc->delay; + if ( time < end_time ) + { + const int mode_flag = 0x80; + + if ( !volume ) + { + // round to next multiple of period + time += (end_time - time + period - 1) / period * period; + + // approximate noise cycling while muted, by shuffling up noise register + // to do: precise muted noise cycling? + if ( !(osc->regs [2] & mode_flag) ) { + int feedback = (this->noise << 13) ^ (this->noise << 14); + this->noise = (feedback & 0x4000) | (this->noise >> 1); + } + } + else + { + struct Blip_Buffer* const output = osc->output; + + // using resampled time avoids conversion in synth.offset() + blip_resampled_time_t rperiod = Blip_resampled_duration( output, period ); + blip_resampled_time_t rtime = Blip_resampled_time( output, time ); + + int noise = this->noise; + int delta = amp * 2 - volume; + const int tap = (osc->regs [2] & mode_flag ? 8 : 13); + + do { + int feedback = (noise << tap) ^ (noise << 14); + time += period; + + if ( (noise + 1) & 2 ) { + // bits 0 and 1 of noise differ + delta = -delta; + Synth_offset_resampled( &this->synth, rtime, delta, output ); + } + + rtime += rperiod; + noise = (feedback & 0x4000) | (noise >> 1); + } + while ( time < end_time ); + + osc->last_amp = (delta + volume) >> 1; + this->noise = noise; + } + } + + osc->delay = time - end_time; +} + diff --git a/apps/codecs/libgme/nes_oscs.h b/apps/codecs/libgme/nes_oscs.h new file mode 100644 index 0000000000..a358e01786 --- /dev/null +++ b/apps/codecs/libgme/nes_oscs.h @@ -0,0 +1,165 @@ +// Private oscillators used by Nes_Apu + +// Nes_Snd_Emu 0.1.8 +#ifndef NES_OSCS_H +#define NES_OSCS_H + +#include "blargg_common.h" +#include "blip_buffer.h" +#include "nes_cpu.h" + +struct Nes_Apu; + +struct Nes_Osc +{ + unsigned char regs [4]; + bool reg_written [4]; + struct Blip_Buffer* output; + int length_counter;// length counter (0 if unused by oscillator) + int delay; // delay until next (potential) transition + int last_amp; // last amplitude oscillator was outputting +}; + +void Osc_clock_length( struct Nes_Osc* this, int halt_mask ); +static inline int Osc_period( struct Nes_Osc* this ) +{ + return (this->regs [3] & 7) * 0x100 + (this->regs [2] & 0xFF); +} + +static inline void Osc_reset( struct Nes_Osc* this ) +{ + this->delay = 0; + this->last_amp = 0; +} + +static inline int Osc_update_amp( struct Nes_Osc* this, int amp ) +{ + int delta = amp - this->last_amp; + this->last_amp = amp; + return delta; +} + +// Nes_Square + +enum { negate_flag = 0x08 }; +enum { shift_mask = 0x07 }; +enum { square_phase_range = 8 }; + +typedef struct Blip_Synth Synth; + +struct Nes_Square +{ + struct Nes_Osc osc; + int envelope; + int env_delay; + int phase; + int sweep_delay; + + Synth* synth; // shared between squares +}; + +static inline void Square_set_synth( struct Nes_Square* this, Synth* s ) { this->synth = s; } + +void Square_clock_sweep( struct Nes_Square* this, int adjust ); +void Square_run( struct Nes_Square* this, nes_time_t, nes_time_t ); + +static inline void Square_reset( struct Nes_Square* this ) +{ + this->sweep_delay = 0; + this->envelope = 0; + this->env_delay = 0; + Osc_reset( &this->osc ); +} + +void Square_clock_envelope( struct Nes_Square* this ); +int Square_volume( struct Nes_Square* this ); + +// Nes_Triangle + +enum { Triangle_phase_range = 16 }; + +struct Nes_Triangle +{ + struct Nes_Osc osc; + + int phase; + int linear_counter; + struct Blip_Synth synth; +}; + +void Triangle_run( struct Nes_Triangle* this, nes_time_t, nes_time_t ); +void Triangle_clock_linear_counter( struct Nes_Triangle* this ); + +static inline void Triangle_reset( struct Nes_Triangle* this ) +{ + this->linear_counter = 0; + this->phase = 1; + Osc_reset( &this->osc ); +} + +// Nes_Noise +struct Nes_Noise +{ + struct Nes_Osc osc; + + int envelope; + int env_delay; + int noise; + struct Blip_Synth synth; +}; + +void Noise_clock_envelope( struct Nes_Noise* this ); +int Noise_volume( struct Nes_Noise* this ); +void Noise_run( struct Nes_Noise* this, nes_time_t, nes_time_t ); + +static inline void Noise_reset( struct Nes_Noise* this ) +{ + this->noise = 1 << 14; + this->envelope = 0; + this->env_delay = 0; + Osc_reset( &this->osc ); +} + +// Nes_Dmc + +enum { loop_flag = 0x40 }; + +struct Nes_Dmc +{ + struct Nes_Osc osc; + + int address; // address of next byte to read + int period; + int buf; + int bits_remain; + int bits; + bool buf_full; + bool silence; + + int dac; + + nes_time_t next_irq; + bool irq_enabled; + bool irq_flag; + bool pal_mode; + bool nonlinear; + + int (*prg_reader)( void*, addr_t ); // needs to be initialized to prg read function + void* prg_reader_data; + + struct Nes_Apu* apu; + + struct Blip_Synth synth; +}; + +void Dmc_start( struct Nes_Dmc* this ); +void Dmc_write_register( struct Nes_Dmc* this, int, int ) ICODE_ATTR; +void Dmc_run( struct Nes_Dmc* this, nes_time_t, nes_time_t ) ICODE_ATTR; +void Dmc_recalc_irq( struct Nes_Dmc* this ) ICODE_ATTR; +void Dmc_fill_buffer( struct Nes_Dmc* this ) ICODE_ATTR; +void Dmc_reload_sample( struct Nes_Dmc* this ) ICODE_ATTR; +void Dmc_reset( struct Nes_Dmc* this ) ICODE_ATTR; + +int Dmc_count_reads( struct Nes_Dmc* this, nes_time_t, nes_time_t* ) ICODE_ATTR; + +#endif diff --git a/apps/codecs/libgme/nes_vrc6_apu.c b/apps/codecs/libgme/nes_vrc6_apu.c new file mode 100644 index 0000000000..0aba81e57f --- /dev/null +++ b/apps/codecs/libgme/nes_vrc6_apu.c @@ -0,0 +1,191 @@ +// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/ + +#include "nes_vrc6_apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void Vrc6_init( struct Nes_Vrc6_Apu* this ) +{ + Synth_init( &this->saw_synth ); + Synth_init( &this->square_synth ); + + Vrc6_output( this, NULL ); + Vrc6_volume( this, 1.0 ); + Vrc6_reset( this ); +} + +void Vrc6_reset( struct Nes_Vrc6_Apu* this ) +{ + this->last_time = 0; + int i; + for ( i = 0; i < vrc6_osc_count; i++ ) + { + struct Vrc6_Osc* osc = &this->oscs [i]; + int j; + for ( j = 0; j < vrc6_reg_count; j++ ) + osc->regs [j] = 0; + osc->delay = 0; + osc->last_amp = 0; + osc->phase = 1; + osc->amp = 0; + } +} + +void Vrc6_output( struct Nes_Vrc6_Apu* this, struct Blip_Buffer* buf ) +{ + int i; + for ( i = 0; i < vrc6_osc_count; i++ ) + Vrc6_osc_output( this, i, buf ); +} + +void run_square( struct Nes_Vrc6_Apu* this, struct Vrc6_Osc* osc, blip_time_t end_time ); +void run_saw( struct Nes_Vrc6_Apu* this, blip_time_t end_time ); +void Vrc6_run_until( struct Nes_Vrc6_Apu* this, blip_time_t time ) +{ + require( time >= this->last_time ); + run_square( this, &this->oscs [0], time ); + run_square( this, &this->oscs [1], time ); + run_saw( this, time ); + this->last_time = time; +} + +void Vrc6_write_osc( struct Nes_Vrc6_Apu* this, blip_time_t time, int osc_index, int reg, int data ) +{ + require( (unsigned) osc_index < vrc6_osc_count ); + require( (unsigned) reg < vrc6_reg_count ); + + Vrc6_run_until( this, time ); + this->oscs [osc_index].regs [reg] = data; +} + +void Vrc6_end_frame( struct Nes_Vrc6_Apu* this, blip_time_t time ) +{ + if ( time > this->last_time ) + Vrc6_run_until( this, time ); + + assert( this->last_time >= time ); + this->last_time -= time; +} + +void run_square( struct Nes_Vrc6_Apu* this, struct Vrc6_Osc* osc, blip_time_t end_time ) +{ + struct Blip_Buffer* output = osc->output; + if ( !output ) + return; + Blip_set_modified( output ); + + int volume = osc->regs [0] & 15; + if ( !(osc->regs [2] & 0x80) ) + volume = 0; + + int gate = osc->regs [0] & 0x80; + int duty = ((osc->regs [0] >> 4) & 7) + 1; + int delta = ((gate || osc->phase < duty) ? volume : 0) - osc->last_amp; + blip_time_t time = this->last_time; + if ( delta ) + { + osc->last_amp += delta; + Synth_offset( &this->square_synth, time, delta, output ); + } + + time += osc->delay; + osc->delay = 0; + int period = Vrc6_osc_period( osc ); + if ( volume && !gate && period > 4 ) + { + if ( time < end_time ) + { + int phase = osc->phase; + + do + { + phase++; + if ( phase == 16 ) + { + phase = 0; + osc->last_amp = volume; + Synth_offset( &this->square_synth, time, volume, output ); + } + if ( phase == duty ) + { + osc->last_amp = 0; + Synth_offset( &this->square_synth, time, -volume, output ); + } + time += period; + } + while ( time < end_time ); + + osc->phase = phase; + } + osc->delay = time - end_time; + } +} + +void run_saw( struct Nes_Vrc6_Apu* this, blip_time_t end_time ) +{ + struct Vrc6_Osc* osc = &this->oscs [2]; + struct Blip_Buffer* output = osc->output; + if ( !output ) + return; + Blip_set_modified( output ); + + int amp = osc->amp; + int amp_step = osc->regs [0] & 0x3F; + blip_time_t time = this->last_time; + int last_amp = osc->last_amp; + if ( !(osc->regs [2] & 0x80) || !(amp_step | amp) ) + { + osc->delay = 0; + int delta = (amp >> 3) - last_amp; + last_amp = amp >> 3; + Synth_offset( &this->saw_synth, time, delta, output ); + } + else + { + time += osc->delay; + if ( time < end_time ) + { + int period = Vrc6_osc_period( osc ) * 2; + int phase = osc->phase; + + do + { + if ( --phase == 0 ) + { + phase = 7; + amp = 0; + } + + int delta = (amp >> 3) - last_amp; + if ( delta ) + { + last_amp = amp >> 3; + Synth_offset( &this->saw_synth, time, delta, output ); + } + + time += period; + amp = (amp + amp_step) & 0xFF; + } + while ( time < end_time ); + + osc->phase = phase; + osc->amp = amp; + } + + osc->delay = time - end_time; + } + + osc->last_amp = last_amp; +} + diff --git a/apps/codecs/libgme/nes_vrc6_apu.h b/apps/codecs/libgme/nes_vrc6_apu.h new file mode 100644 index 0000000000..540438f4a2 --- /dev/null +++ b/apps/codecs/libgme/nes_vrc6_apu.h @@ -0,0 +1,62 @@ +// Konami VRC6 sound chip emulator + +// Nes_Snd_Emu 0.1.8 +#ifndef NES_VRC6_APU_H +#define NES_VRC6_APU_H + +#include "blargg_common.h" +#include "blip_buffer.h" + +enum { vrc6_osc_count = 3 }; +enum { vrc6_reg_count = 3 }; +enum { vrc6_base_addr = 0x9000 }; +enum { vrc6_addr_step = 0x1000 }; + +struct Vrc6_Osc +{ + uint8_t regs [vrc6_reg_count]; + struct Blip_Buffer* output; + int delay; + int last_amp; + int phase; + int amp; // only used by saw +}; + +static inline int Vrc6_osc_period( struct Vrc6_Osc* this ) +{ + return (this->regs [2] & 0x0F) * 0x100 + this->regs [1] + 1; +} + +struct Nes_Vrc6_Apu { + struct Vrc6_Osc oscs [vrc6_osc_count]; + blip_time_t last_time; + + struct Blip_Synth saw_synth; + struct Blip_Synth square_synth; +}; + +// See Nes_Apu.h for reference +void Vrc6_init( struct Nes_Vrc6_Apu* this ); +void Vrc6_reset( struct Nes_Vrc6_Apu* this ); +void Vrc6_output( struct Nes_Vrc6_Apu* this, struct Blip_Buffer* ); +void Vrc6_end_frame( struct Nes_Vrc6_Apu* this, blip_time_t ) ICODE_ATTR; + +// Oscillator 0 write-only registers are at $9000-$9002 +// Oscillator 1 write-only registers are at $A000-$A002 +// Oscillator 2 write-only registers are at $B000-$B002 +void Vrc6_write_osc( struct Nes_Vrc6_Apu* this, blip_time_t, int osc, int reg, int data ) ICODE_ATTR; + +static inline void Vrc6_osc_output( struct Nes_Vrc6_Apu* this, int i, struct Blip_Buffer* buf ) +{ + assert( (unsigned) i < vrc6_osc_count ); + this->oscs [i].output = buf; +} + +static inline void Vrc6_volume( struct Nes_Vrc6_Apu* this, double v ) +{ + double const factor = 0.0967 * 2; + Synth_volume( &this->saw_synth, factor / 31 * v ); + Synth_volume( &this->square_synth, factor * 0.5 / 15 * v ); +} + +#endif diff --git a/apps/codecs/libgme/nes_vrc7_apu.c b/apps/codecs/libgme/nes_vrc7_apu.c new file mode 100644 index 0000000000..d8768bfc7e --- /dev/null +++ b/apps/codecs/libgme/nes_vrc7_apu.c @@ -0,0 +1,89 @@ + +#include "nes_vrc7_apu.h" +#include "blargg_source.h" + +int const period = 36; // NES CPU clocks per FM clock + +void Vrc7_init( struct Nes_Vrc7_Apu* this ) +{ + Synth_init( &this->synth ); + + OPLL_new ( &this->opll, 3579545, 3579545 / 72 ); + OPLL_reset_patch( &this->opll, OPLL_VRC7_TONE ); + + this->osc.output = 0; + this->osc.last_amp = 0; + this->mask = 0; + + Vrc7_volume( this, 1.0 ); + Vrc7_reset( this ); +} + +void Vrc7_reset( struct Nes_Vrc7_Apu* this ) +{ + this->addr = 0; + this->next_time = 0; + this->osc.last_amp = 0; + + OPLL_reset (&this->opll); + OPLL_setMask(&this->opll, this->mask); +} + +void Vrc7_set_rate( struct Nes_Vrc7_Apu* this, double r ) +{ + OPLL_set_quality( &this->opll, r < 44100 ? 0 : 1 ); + OPLL_set_rate( &this->opll, (e_uint32)r ); +} + +void Vrc7_write_reg( struct Nes_Vrc7_Apu* this, int data ) +{ + this->addr = data; +} + +void Vrc7_run_until( struct Nes_Vrc7_Apu* this, blip_time_t end_time ); +void Vrc7_write_data( struct Nes_Vrc7_Apu* this, blip_time_t time, int data ) +{ + if ( time > this->next_time ) + Vrc7_run_until( this, time ); + + OPLL_writeIO( &this->opll, 0, this->addr ); + OPLL_writeIO( &this->opll, 1, data ); +} + +void Vrc7_end_frame( struct Nes_Vrc7_Apu* this, blip_time_t time ) +{ + if ( time > this->next_time ) + Vrc7_run_until( this, time ); + + this->next_time -= time; + assert( this->next_time >= 0 ); + + if ( this->osc.output ) + Blip_set_modified( this->osc.output ); +} + +void Vrc7_run_until( struct Nes_Vrc7_Apu* this, blip_time_t end_time ) +{ + require( end_time > this->next_time ); + + blip_time_t time = this->next_time; + OPLL* opll = &this->opll; // cache + struct Blip_Buffer* const output = this-> osc.output; + if ( output ) + { + do + { + int amp = OPLL_calc( opll ) << 1; + int delta = amp - this->osc.last_amp; + if ( delta ) + { + this->osc.last_amp = amp; + Synth_offset_inline( &this->synth, time, delta, output ); + } + time += period; + } + while ( time < end_time ); + } + + this->next_time = time; +} diff --git a/apps/codecs/libgme/nes_vrc7_apu.h b/apps/codecs/libgme/nes_vrc7_apu.h new file mode 100644 index 0000000000..5453e6b403 --- /dev/null +++ b/apps/codecs/libgme/nes_vrc7_apu.h @@ -0,0 +1,52 @@ +// Konami VRC7 sound chip emulator + +#ifndef NES_VRC7_APU_H +#define NES_VRC7_APU_H + +#include "blargg_common.h" +#include "blip_buffer.h" + +#include "emu2413.h" + +enum { vrc7_osc_count = 6 }; + +struct vrc7_osc_t { + struct Blip_Buffer* output; + int last_amp; +}; + +struct Nes_Vrc7_Apu { + OPLL opll; + int addr; + blip_time_t next_time; + struct vrc7_osc_t osc; + struct Blip_Synth synth; + e_uint32 mask; +}; + +// See Nes_Apu.h for reference +void Vrc7_init( struct Nes_Vrc7_Apu* this ); +void Vrc7_reset( struct Nes_Vrc7_Apu* this ); +void Vrc7_set_rate( struct Nes_Vrc7_Apu* this, double r ); +void Vrc7_end_frame( struct Nes_Vrc7_Apu* this, blip_time_t ) ICODE_ATTR; + +void Vrc7_write_reg( struct Nes_Vrc7_Apu* this, int reg ) ICODE_ATTR; +void Vrc7_write_data( struct Nes_Vrc7_Apu* this, blip_time_t, int data ) ICODE_ATTR; + +void output_changed( struct Nes_Vrc7_Apu* this ); +static inline void Vrc7_set_output( struct Nes_Vrc7_Apu* this, int i, struct Blip_Buffer* buf ) +{ + assert( (unsigned) i < vrc7_osc_count ); + this->mask |= 1 << i; + + // Will use OPLL_setMask to mute voices + if ( buf ) { + this->mask ^= 1 << i; + this->osc.output = buf; + } +} + +// DB2LIN_AMP_BITS == 11, * 2 +static inline void Vrc7_volume( struct Nes_Vrc7_Apu* this, double v ) { Synth_volume( &this->synth, 1.0 / 3 / 4096 * v ); } + +#endif diff --git a/apps/codecs/libgme/nsf_cpu.c b/apps/codecs/libgme/nsf_cpu.c new file mode 100644 index 0000000000..1f44bd3c3c --- /dev/null +++ b/apps/codecs/libgme/nsf_cpu.c @@ -0,0 +1,115 @@ +// Normal cpu for NSF emulator + +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "nsf_emu.h" + +#include "blargg_endian.h" + +#ifdef BLARGG_DEBUG_H + //#define CPU_LOG_START 1000000 + //#include "nes_cpu_log.h" + #undef LOG_MEM +#endif + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#ifndef LOG_MEM + #define LOG_MEM( addr, str, data ) data +#endif + +int read_mem( struct Nsf_Emu* this, addr_t addr ) +{ + int result = this->low_ram [addr & (low_ram_size-1)]; // also handles wrap-around + if ( addr & 0xE000 ) + { + result = *Cpu_get_code( &this->cpu, addr ); + if ( addr < sram_addr ) + { + if ( addr == apu_status_addr ) + result = Apu_read_status( &this->apu, Cpu_time( &this->cpu ) ); + else + result = cpu_read( this, addr ); + } + } + return LOG_MEM( addr, ">", result ); +} + +void write_mem( struct Nsf_Emu* this, addr_t addr, int data ) +{ + (void) LOG_MEM( addr, "<", data ); + + int offset = addr - sram_addr; + if ( (unsigned) offset < sram_size ) + { + sram( this ) [offset] = data; + } + else + { + // after sram because cpu handles most low_ram accesses internally already + int temp = addr & (low_ram_size-1); // also handles wrap-around + if ( !(addr & 0xE000) ) + { + this->low_ram [temp] = data; + } + else + { + int bank = addr - banks_addr; + if ( (unsigned) bank < bank_count ) + { + write_bank( this, bank, data ); + } + else if ( (unsigned) (addr - apu_io_addr) < apu_io_size ) + { + Apu_write_register( &this->apu, Cpu_time( &this->cpu ), addr, data ); + } + else + { + #ifndef NSF_EMU_APU_ONLY + // 0x8000-0xDFFF is writable + int i = addr - 0x8000; + if ( fds_enabled( this ) && (unsigned) i < fdsram_size ) + fdsram( this ) [i] = data; + else + #endif + cpu_write( this, addr, data ); + } + } + } +} + +#define READ_LOW( addr ) (LOG_MEM( addr, ">", this->low_ram [addr] )) +#define WRITE_LOW( addr, data ) (LOG_MEM( addr, "<", this->low_ram [addr] = data )) + +#define CAN_WRITE_FAST( addr ) (addr < low_ram_size) +#define WRITE_FAST WRITE_LOW + +// addr < 0x2000 || addr >= 0x8000 +#define CAN_READ_FAST( addr ) ((addr ^ 0x8000) < 0xA000) +#define READ_FAST( addr, out ) (LOG_MEM( addr, ">", out = READ_CODE( addr ) )) + +#define READ_MEM( addr ) read_mem( this, addr ) +#define WRITE_MEM( addr, data ) write_mem( this, addr, data ) + +#define CPU_BEGIN \ +bool run_cpu_until( struct Nsf_Emu* this, nes_time_t end ) \ +{ \ + struct Nes_Cpu* cpu = &this->cpu; \ + Cpu_set_end_time( cpu, end ); \ + if ( *Cpu_get_code( cpu, cpu->r.pc ) != halt_opcode ) \ + { + #include "nes_cpu_run.h" + } + return Cpu_time_past_end( cpu ) < 0; +} diff --git a/apps/codecs/libgme/nsf_emu.c b/apps/codecs/libgme/nsf_emu.c new file mode 100644 index 0000000000..c805780cb1 --- /dev/null +++ b/apps/codecs/libgme/nsf_emu.c @@ -0,0 +1,1105 @@ +// Game_Music_Emu 0.5.5. http://www.slack.net/~ant/ + +#include "nsf_emu.h" +#include "multi_buffer.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +const char gme_wrong_file_type [] ICONST_ATTR = "Wrong file type for this emulator"; +long const clock_divisor = 12; + +int const stereo = 2; // number of channels for stereo +int const silence_max = 6; // seconds +int const silence_threshold = 0x10; +long const fade_block_size = 512; +int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) + +// number of frames until play interrupts init +int const initial_play_delay = 7; // KikiKaikai needed this to work +int const rom_addr = 0x8000; + +void clear_track_vars( struct Nsf_Emu* this ) +{ + this->current_track = -1; + this->out_time = 0; + this->emu_time = 0; + this->emu_track_ended_ = true; + this->track_ended = true; + this->fade_start = INT_MAX / 2 + 1; + this->fade_step = 1; + this->silence_time = 0; + this->silence_count = 0; + this->buf_remain = 0; +} + +static int pcm_read( void* emu, addr_t addr ) +{ + return *Cpu_get_code( &((struct Nsf_Emu*) emu)->cpu, addr ); +} + +void Nsf_init( struct Nsf_Emu* this ) +{ + this->sample_rate = 0; + this->mute_mask_ = 0; + this->tempo = 1.0; + this->gain = 1.0; + + // defaults + this->max_initial_silence = 2; + this->ignore_silence = false; + this->voice_count = 0; + + // Set sound gain + Sound_set_gain( this, 1.2 ); + + // Unload + clear_track_vars( this ); + + // Init rom + Rom_init( &this->rom, 0x1000 ); + + // Init & clear nsfe info + Info_init( &this->info ); + Info_unload( &this->info ); // TODO: extremely hacky! + + Cpu_init( &this->cpu ); + Apu_init( &this->apu ); + Apu_dmc_reader( &this->apu, pcm_read, this ); +} + +// Setup + +blargg_err_t init_sound( struct Nsf_Emu* this ) +{ + /* if ( header_.chip_flags & ~(fds_flag | namco_flag | vrc6_flag | fme7_flag) ) + warning( "Uses unsupported audio expansion hardware" ); **/ + + this->voice_count = apu_osc_count; + + double adjusted_gain = 1.0 / 0.75 * this->gain; + + #ifdef NSF_EMU_APU_ONLY + { + if ( this->header_.chip_flags ) + set_warning( "Uses unsupported audio expansion hardware" ); + } + #else + { + if ( vrc6_enabled( this ) ) + { + Vrc6_init( &this->vrc6 ); + adjusted_gain *= 0.75; + + this->voice_count += vrc6_osc_count; + } + + if ( fme7_enabled( this ) ) + { + Fme7_init( &this->fme7 ); + adjusted_gain *= 0.75; + + this->voice_count += fme7_osc_count; + } + + if ( mmc5_enabled( this ) ) + { + Mmc5_init( &this->mmc5 ); + adjusted_gain *= 0.75; + + this->voice_count += mmc5_osc_count; + } + + if ( fds_enabled( this ) ) + { + Fds_init( &this->fds ); + adjusted_gain *= 0.75; + + this->voice_count += fds_osc_count ; + } + + if ( namco_enabled( this ) ) + { + Namco_init( &this->namco ); + adjusted_gain *= 0.75; + + this->voice_count += namco_osc_count; + } + + if ( vrc7_enabled( this ) ) + { + #ifndef NSF_EMU_NO_VRC7 + Vrc7_init( &this->vrc7 ); + Vrc7_set_rate( &this->vrc7, this->sample_rate ); + #endif + + adjusted_gain *= 0.75; + + this->voice_count += vrc7_osc_count; + } + + if ( vrc7_enabled( this ) ) Vrc7_volume( &this->vrc7, adjusted_gain ); + if ( namco_enabled( this ) ) Namco_volume( &this->namco, adjusted_gain ); + if ( vrc6_enabled( this ) ) Vrc6_volume( &this->vrc6, adjusted_gain ); + if ( fme7_enabled( this ) ) Fme7_volume( &this->fme7, adjusted_gain ); + if ( mmc5_enabled( this ) ) Apu_volume( &this->mmc5.apu, adjusted_gain ); + if ( fds_enabled( this ) ) Fds_volume( &this->fds, adjusted_gain ); + } + #endif + + if ( adjusted_gain > this->gain ) + adjusted_gain = this->gain; + + Apu_volume( &this->apu, adjusted_gain ); + + return 0; +} + +// Header stuff +bool valid_tag( struct header_t* this ) +{ + return 0 == memcmp( this->tag, "NESM\x1A", 5 ); +} + +// True if file supports only PAL speed +static bool pal_only( struct header_t* this ) +{ + return (this->speed_flags & 3) == 1; +} + +static double clock_rate( struct header_t* this ) +{ + return pal_only( this ) ? 1662607.125 : 1789772.727272727; +} + +int play_period( struct header_t* this ) +{ + // NTSC + int clocks = 29780; + int value = 0x411A; + byte const* rate_ptr = this->ntsc_speed; + + // PAL + if ( pal_only( this ) ) + { + clocks = 33247; + value = 0x4E20; + rate_ptr = this->pal_speed; + } + + // Default rate + int rate = get_le16( rate_ptr ); + if ( rate == 0 ) + rate = value; + + // Custom rate + if ( rate != value ) + clocks = (int) (rate * clock_rate( this ) * (1.0/1000000.0)); + + return clocks; +} + +// Gets address, given pointer to it in file header. If zero, returns rom_addr. +addr_t get_addr( byte const in [] ) +{ + addr_t addr = get_le16( in ); + if ( addr == 0 ) + addr = rom_addr; + return addr; +} + +static blargg_err_t check_nsf_header( struct header_t* h ) +{ + if ( !valid_tag( h ) ) + return gme_wrong_file_type; + return 0; +} + +blargg_err_t Nsf_load( struct Nsf_Emu* this, void* data, long size ) +{ + // Unload + Info_unload( &this->info ); // TODO: extremely hacky! + this->m3u.size = 0; + + this->voice_count = 0; + clear_track_vars( this ); + + assert( offsetof (struct header_t,unused [4]) == header_size ); + + if ( !memcmp( data, "NESM\x1A", 5 ) ) { + Nsf_disable_playlist( this, true ); + + RETURN_ERR( Rom_load( &this->rom, data, size, header_size, &this->header, 0 ) ); + return Nsf_post_load( this ); + } + + blargg_err_t err = Info_load( &this->info, data, size, this ); + Nsf_disable_playlist( this, false ); + return err; +} + +blargg_err_t Nsf_post_load( struct Nsf_Emu* this ) +{ + RETURN_ERR( check_nsf_header( &this->header ) ); + + /* if ( header_.vers != 1 ) + warning( "Unknown file version" ); */ + + // set up data + addr_t load_addr = get_le16( this->header.load_addr ); + + /* if ( load_addr < (fds_enabled() ? sram_addr : rom_addr) ) + warning( "Load address is too low" ); */ + + Rom_set_addr( &this->rom, load_addr % this->rom.bank_size ); + + /* if ( header_.vers != 1 ) + warning( "Unknown file version" ); */ + + set_play_period( this, play_period( &this->header ) ); + + // sound and memory + blargg_err_t err = init_sound( this ); + if ( err ) + return err; + + // Post load + Sound_set_tempo( this, this->tempo ); + + // Remute voices + Sound_mute_voices( this, this->mute_mask_ ); + + // Set track_count + this->track_count = this->header.track_count; + + // Change clock rate & setup buffer + this->clock_rate__ = (long) (clock_rate( &this->header ) + 0.5); + Buffer_clock_rate( &this->stereo_buf, this->clock_rate__ ); + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); + return 0; +} + +void Nsf_disable_playlist( struct Nsf_Emu* this, bool b ) +{ + Info_disable_playlist( &this->info, b ); + this->track_count = this->info.track_count; +} + +void Nsf_clear_playlist( struct Nsf_Emu* this ) +{ + Nsf_disable_playlist( this, true ); +} + +void write_bank( struct Nsf_Emu* this, int bank, int data ) +{ + // Find bank in ROM + int offset = mask_addr( data * this->rom.bank_size, this->rom.mask ); + /* if ( offset >= rom.size() ) + warning( "invalid bank" ); */ + void const* rom_data = Rom_at_addr( &this->rom, offset ); + + #ifndef NSF_EMU_APU_ONLY + if ( bank < bank_count - fds_banks && fds_enabled( this ) ) + { + // TODO: FDS bank switching is kind of hacky, might need to + // treat ROM as RAM so changes won't get lost when switching. + byte* out = sram( this ); + if ( bank >= fds_banks ) + { + out = fdsram( this ); + bank -= fds_banks; + } + memcpy( &out [bank * this->rom.bank_size], rom_data, this->rom.bank_size ); + return; + } + #endif + + if ( bank >= fds_banks ) + Cpu_map_code( &this->cpu, (bank + 6) * this->rom.bank_size, this->rom.bank_size, rom_data, false ); +} + +void map_memory( struct Nsf_Emu* this ) +{ + // Map standard things + Cpu_reset( &this->cpu, unmapped_code( this ) ); + Cpu_map_code( &this->cpu, 0, 0x2000, this->low_ram, low_ram_size ); // mirrored four times + Cpu_map_code( &this->cpu, sram_addr, sram_size, sram( this ), 0 ); + + // Determine initial banks + byte banks [bank_count]; + static byte const zero_banks [sizeof this->header.banks] = { 0 }; + if ( memcmp( this->header.banks, zero_banks, sizeof zero_banks ) ) + { + banks [0] = this->header.banks [6]; + banks [1] = this->header.banks [7]; + memcpy( banks + fds_banks, this->header.banks, sizeof this->header.banks ); + } + else + { + // No initial banks, so assign them based on load_addr + int i, first_bank = (get_addr( this->header.load_addr ) - sram_addr) / this->rom.bank_size; + unsigned total_banks = this->rom.size / this->rom.bank_size; + for ( i = bank_count; --i >= 0; ) + { + int bank = i - first_bank; + if ( (unsigned) bank >= total_banks ) + bank = 0; + banks [i] = bank; + } + } + + // Map banks + int i; + for ( i = (fds_enabled( this ) ? 0 : fds_banks); i < bank_count; ++i ) + write_bank( this, i, banks [i] ); + + // Map FDS RAM + if ( fds_enabled( this ) ) + Cpu_map_code( &this->cpu, rom_addr, fdsram_size, fdsram( this ), 0 ); +} + +void set_voice( struct Nsf_Emu* this, int i, struct Blip_Buffer* buf, struct Blip_Buffer* left, struct Blip_Buffer* right) +{ +#if defined(ROCKBOX) + (void) left; + (void) right; +#endif + + if ( i < apu_osc_count ) + { + Apu_osc_output( &this->apu, i, buf ); + return; + } + i -= apu_osc_count; + + #ifndef NSF_EMU_APU_ONLY + { + if ( vrc6_enabled( this ) && (i -= vrc6_osc_count) < 0 ) + { + Vrc6_osc_output( &this->vrc6, i + vrc6_osc_count, buf ); + return; + } + + if ( fme7_enabled( this ) && (i -= fme7_osc_count) < 0 ) + { + Fme7_osc_output( &this->fme7, i + fme7_osc_count, buf ); + return; + } + + if ( mmc5_enabled( this ) && (i -= mmc5_osc_count) < 0 ) + { + Mmc5_set_output( &this->mmc5, i + mmc5_osc_count, buf ); + return; + } + + if ( fds_enabled( this ) && (i -= fds_osc_count) < 0 ) + { + Fds_set_output( &this->fds, i + fds_osc_count, buf ); + return; + } + + if ( namco_enabled( this ) && (i -= namco_osc_count) < 0 ) + { + Namco_osc_output( &this->namco, i + namco_osc_count, buf ); + return; + } + + if ( vrc7_enabled( this ) && (i -= vrc7_osc_count) < 0 ) + { + Vrc7_set_output( &this->vrc7, i + vrc7_osc_count, buf ); + return; + } + } + #endif +} + +// Emulation + +// Music Emu + +blargg_err_t Nsf_set_sample_rate( struct Nsf_Emu* this, long rate ) +{ + require( !this->sample_rate ); // sample rate can't be changed once set + Buffer_init( &this->stereo_buf ); + RETURN_ERR( Buffer_set_sample_rate( &this->stereo_buf, rate, 1000 / 20 ) ); + + // Set bass frequency + Buffer_bass_freq( &this->stereo_buf, 80 ); + + this->sample_rate = rate; + return 0; +} + +void Sound_mute_voice( struct Nsf_Emu* this, int index, bool mute ) +{ + require( (unsigned) index < (unsigned) this->voice_count ); + int bit = 1 << index; + int mask = this->mute_mask_ | bit; + if ( !mute ) + mask ^= bit; + Sound_mute_voices( this, mask ); +} + +void Sound_mute_voices( struct Nsf_Emu* this, int mask ) +{ + require( this->sample_rate ); // sample rate must be set first + this->mute_mask_ = mask; + + int i; + for ( i = this->voice_count; i--; ) + { + if ( mask & (1 << i) ) + { + set_voice( this, i, 0, 0, 0 ); + } + else + { + struct channel_t ch = Buffer_channel( &this->stereo_buf ); + assert( (ch.center && ch.left && ch.right) || + (!ch.center && !ch.left && !ch.right) ); // all or nothing + set_voice( this, i, ch.center, ch.left, ch.right ); + } + } +} + +void Sound_set_tempo( struct Nsf_Emu* this, double t ) +{ + require( this->sample_rate ); // sample rate must be set first + double const min = 0.02; + double const max = 4.00; + if ( t < min ) t = min; + if ( t > max ) t = max; + this->tempo = t; + + set_play_period( this, (int) (play_period( &this->header ) / t) ); + + Apu_set_tempo( &this->apu, t ); + +#ifndef NSF_EMU_APU_ONLY + if ( fds_enabled( this ) ) + Fds_set_tempo( &this->fds, t ); +#endif +} + +inline void push_byte( struct Nsf_Emu* this, int b ) +{ + this->low_ram [0x100 + this->cpu.r.sp--] = b; +} + +// Jumps to routine, given pointer to address in file header. Pushes idle_addr +// as return address, NOT old PC. +void jsr_then_stop( struct Nsf_Emu* this, byte const addr [] ) +{ + this->cpu.r.pc = get_addr( addr ); + push_byte( this, (idle_addr - 1) >> 8 ); + push_byte( this, (idle_addr - 1) ); +} + +int cpu_read( struct Nsf_Emu* this, addr_t addr ) +{ + #ifndef NSF_EMU_APU_ONLY + { + if ( namco_enabled( this ) && addr == namco_data_reg_addr ) + return Namco_read_data( &this->namco ); + + if ( fds_enabled( this ) && (unsigned) (addr - fds_io_addr) < fds_io_size ) + return Fds_read( &this->fds, Cpu_time( &this->cpu ), addr ); + + if ( mmc5_enabled( this ) ) { + int i = addr - 0x5C00; + if ( (unsigned) i < mmc5_exram_size ) + return this->mmc5.exram [i]; + + int m = addr - 0x5205; + if ( (unsigned) m < 2 ) + return (this->mmc5_mul [0] * this->mmc5_mul [1]) >> (m * 8) & 0xFF; + } + } + #endif + + /* Unmapped read */ + return addr >> 8; +} + +int unmapped_read( struct Nsf_Emu* this, addr_t addr ) +{ + (void) this; + + switch ( addr ) + { + case 0x2002: + case 0x4016: + case 0x4017: + return addr >> 8; + } + + // Unmapped read + return addr >> 8; +} + +void cpu_write( struct Nsf_Emu* this, addr_t addr, int data ) +{ + #ifndef NSF_EMU_APU_ONLY + { + nes_time_t time = Cpu_time( &this->cpu ); + if ( fds_enabled( this) && (unsigned) (addr - fds_io_addr) < fds_io_size ) + { + Fds_write( &this->fds, time, addr, data ); + return; + } + + if ( namco_enabled( this) ) + { + if ( addr == namco_addr_reg_addr ) + { + Namco_write_addr( &this->namco, data ); + return; + } + + if ( addr == namco_data_reg_addr ) + { + Namco_write_data( &this->namco, time, data ); + return; + } + } + + if ( vrc6_enabled( this) ) + { + int reg = addr & (vrc6_addr_step - 1); + int osc = (unsigned) (addr - vrc6_base_addr) / vrc6_addr_step; + if ( (unsigned) osc < vrc6_osc_count && (unsigned) reg < vrc6_reg_count ) + { + Vrc6_write_osc( &this->vrc6, time, osc, reg, data ); + return; + } + } + + if ( fme7_enabled( this) && addr >= fme7_latch_addr ) + { + switch ( addr & fme7_addr_mask ) + { + case fme7_latch_addr: + Fme7_write_latch( &this->fme7, data ); + return; + + case fme7_data_addr: + Fme7_write_data( &this->fme7, time, data ); + return; + } + } + + if ( mmc5_enabled( this) ) + { + if ( (unsigned) (addr - mmc5_regs_addr) < mmc5_regs_size ) + { + Mmc5_write_register( &this->mmc5, time, addr, data ); + return; + } + + int m = addr - 0x5205; + if ( (unsigned) m < 2 ) + { + this->mmc5_mul [m] = data; + return; + } + + int i = addr - 0x5C00; + if ( (unsigned) i < mmc5_exram_size ) + { + this->mmc5.exram [i] = data; + return; + } + } + + if ( vrc7_enabled( this) ) + { + if ( addr == 0x9010 ) + { + Vrc7_write_reg( &this->vrc7, data ); + return; + } + + if ( (unsigned) (addr - 0x9028) <= 0x08 ) + { + Vrc7_write_data( &this->vrc7, time, data ); + return; + } + } + } + #endif + + // Unmapped_write +} + +void unmapped_write( struct Nsf_Emu* this, addr_t addr, int data ) +{ + (void) data; + + switch ( addr ) + { + case 0x8000: // some write to $8000 and $8001 repeatedly + case 0x8001: + case 0x4800: // probably namco sound mistakenly turned on in MCK + case 0xF800: + case 0xFFF8: // memory mapper? + return; + } + + if ( mmc5_enabled( this ) && addr == 0x5115 ) return; + + // FDS memory + if ( fds_enabled( this ) && (unsigned) (addr - 0x8000) < 0x6000 ) return; +} + +void fill_buf( struct Nsf_Emu* this ); +blargg_err_t Nsf_start_track( struct Nsf_Emu* this, int track ) +{ + clear_track_vars( this ); + + // Remap track if playlist available + if ( this->m3u.size > 0 ) { + struct entry_t* e = &this->m3u.entries[track]; + track = e->track; + } + else track = Info_remap_track( &this->info, track ); + + this->current_track = track; + Buffer_clear( &this->stereo_buf ); + + #ifndef NSF_EMU_APU_ONLY + if ( mmc5_enabled( this ) ) + { + this->mmc5_mul [0] = 0; + this->mmc5_mul [1] = 0; + memset( this->mmc5.exram, 0, mmc5_exram_size ); + } + + if ( fds_enabled( this ) ) Fds_reset( &this->fds ); + if ( namco_enabled( this ) ) Namco_reset( &this->namco ); + if ( vrc6_enabled( this ) ) Vrc6_reset( &this->vrc6 ); + if ( fme7_enabled( this ) ) Fme7_reset( &this->fme7 ); + if ( mmc5_enabled( this ) ) Apu_reset( &this->mmc5.apu, false, 0 ); + if ( vrc7_enabled( this ) ) Vrc7_reset( &this->vrc7 ); + #endif + + int speed_flags = 0; + #ifdef NSF_EMU_EXTRA_FLAGS + speed_flags = this->header.speed_flags; + #endif + + Apu_reset( &this->apu, pal_only( &this->header ), (speed_flags & 0x20) ? 0x3F : 0 ); + Apu_write_register( &this->apu, 0, 0x4015, 0x0F ); + Apu_write_register( &this->apu, 0, 0x4017, (speed_flags & 0x10) ? 0x80 : 0 ); + + memset( unmapped_code( this ), halt_opcode, unmapped_size ); + memset( this->low_ram, 0, low_ram_size ); + memset( sram( this ), 0, sram_size ); + + map_memory( this ); + + // Arrange time of first call to play routine + this->play_extra = 0; + this->next_play = this->play_period; + + this->play_delay = initial_play_delay; + this->saved_state.pc = idle_addr; + + // Setup for call to init routine + this->cpu.r.a = track; + this->cpu.r.x = pal_only( &this->header ); + this->cpu.r.sp = 0xFF; + jsr_then_stop( this, this->header.init_addr ); + /* if ( this->cpu.r.pc < get_addr( header.load_addr ) ) + warning( "Init address < load address" ); */ + + this->emu_track_ended_ = false; + this->track_ended = false; + + if ( !this->ignore_silence ) + { + // play until non-silence or end of track + long end; + for ( end = this->max_initial_silence * stereo * this->sample_rate; this->emu_time < end; ) + { + fill_buf( this ); + if ( this->buf_remain | (int) this->emu_track_ended_ ) + break; + } + + this->emu_time = this->buf_remain; + this->out_time = 0; + this->silence_time = 0; + this->silence_count = 0; + } + /* return track_ended() ? warning() : 0; */ + return 0; +} + +void run_once( struct Nsf_Emu* this, nes_time_t end ) +{ + // Emulate until next play call if possible + if ( run_cpu_until( this, min( this->next_play, end ) ) ) + { + // Halt instruction encountered + + if ( this->cpu.r.pc != idle_addr ) + { + // special_event( "illegal instruction" ); + Cpu_set_time( &this->cpu, this->cpu.end_time ); + return; + } + + // Init/play routine returned + this->play_delay = 1; // play can now be called regularly + + if ( this->saved_state.pc == idle_addr ) + { + // nothing to run + nes_time_t t = this->cpu.end_time; + if ( Cpu_time( &this->cpu ) < t ) + Cpu_set_time( &this->cpu, t ); + } + else + { + // continue init routine that was interrupted by play routine + this->cpu.r = this->saved_state; + this->saved_state.pc = idle_addr; + } + } + + if ( Cpu_time( &this->cpu ) >= this->next_play ) + { + // Calculate time of next call to play routine + this->play_extra ^= 1; // extra clock every other call + this->next_play += this->play_period + this->play_extra; + + // Call routine if ready + if ( this->play_delay && !--this->play_delay ) + { + // Save state if init routine is still running + if ( this->cpu.r.pc != idle_addr ) + { + check( this->saved_state.pc == idle_addr ); + this->saved_state = this->cpu.r; + // special_event( "play called during init" ); + } + + jsr_then_stop( this, this->header.play_addr ); + } + } +} + +void run_until( struct Nsf_Emu* this, nes_time_t end ) +{ + while ( Cpu_time( &this->cpu ) < end ) + run_once( this, end ); +} + +void end_frame( struct Nsf_Emu* this, nes_time_t end ) +{ + if ( Cpu_time( &this->cpu ) < end ) + run_until( this, end ); + Cpu_adjust_time( &this->cpu, -end ); + + // Localize to new time frame + this->next_play -= end; + check( this->next_play >= 0 ); + if ( this->next_play < 0 ) + this->next_play = 0; + + Apu_end_frame( &this->apu, end ); + + #ifndef NSF_EMU_APU_ONLY + if ( fds_enabled( this ) ) Fds_end_frame( &this->fds, end ); + if ( fme7_enabled( this ) ) Fme7_end_frame( &this->fme7, end ); + if ( mmc5_enabled( this ) ) Apu_end_frame( &this->mmc5.apu, end ); + if ( namco_enabled( this ) ) Namco_end_frame( &this->namco, end ); + if ( vrc6_enabled( this ) ) Vrc6_end_frame( &this->vrc6, end ); + if ( vrc7_enabled( this ) ) Vrc7_end_frame( &this->vrc7, end ); + #endif +} + +// Tell/Seek + +blargg_long msec_to_samples( long sample_rate, blargg_long msec ) +{ + blargg_long sec = msec / 1000; + msec -= sec * 1000; + return (sec * sample_rate + msec * sample_rate / 1000) * stereo; +} + +long Track_tell( struct Nsf_Emu* this ) +{ + blargg_long rate = this->sample_rate * stereo; + blargg_long sec = this->out_time / rate; + return sec * 1000 + (this->out_time - sec * rate) * 1000 / rate; +} + +blargg_err_t Track_seek( struct Nsf_Emu* this, long msec ) +{ + blargg_long time = msec_to_samples( this->sample_rate, msec ); + if ( time < this->out_time ) + RETURN_ERR( Nsf_start_track( this, this->current_track ) ); + return Track_skip( this, time - this->out_time ); +} + +blargg_err_t skip_( struct Nsf_Emu* this, long count ) ICODE_ATTR; +blargg_err_t Track_skip( struct Nsf_Emu* this, long count ) +{ + require( this->current_track >= 0 ); // start_track() must have been called already + this->out_time += count; + + // remove from silence and buf first + { + long n = min( count, this->silence_count ); + this->silence_count -= n; + count -= n; + + n = min( count, this->buf_remain ); + this->buf_remain -= n; + count -= n; + } + + if ( count && !this->emu_track_ended_ ) + { + this->emu_time += count; + // End track if error + if ( skip_( this, count ) ) + this->emu_track_ended_ = true; + } + + if ( !(this->silence_count | this->buf_remain) ) // caught up to emulator, so update track ended + this->track_ended |= this->emu_track_ended_; + + return 0; +} + +blargg_err_t play_( struct Nsf_Emu* this, long count, sample_t* out ) ICODE_ATTR; +blargg_err_t skip_( struct Nsf_Emu* this, long count ) +{ + // for long skip, mute sound + const long threshold = 30000; + if ( count > threshold ) + { + int saved_mute = this->mute_mask_; + Sound_mute_voices( this, ~0 ); + + while ( count > threshold / 2 && !this->emu_track_ended_ ) + { + RETURN_ERR( play_( this, buf_size, this->buf ) ); + count -= buf_size; + } + + Sound_mute_voices( this, saved_mute ); + } + + while ( count && !this->emu_track_ended_ ) + { + long n = buf_size; + if ( n > count ) + n = count; + count -= n; + RETURN_ERR( play_( this, n, this->buf ) ); + } + return 0; +} + +// Fading + +void Track_set_fade( struct Nsf_Emu* this, long start_msec, long length_msec ) +{ + this->fade_step = this->sample_rate * length_msec / (fade_block_size * fade_shift * 1000 / stereo); + this->fade_start = msec_to_samples( this->sample_rate, start_msec ); +} + +// unit / pow( 2.0, (double) x / step ) +static int int_log( blargg_long x, int step, int unit ) +{ + int shift = x / step; + int fraction = (x - shift * step) * unit / step; + return ((unit - fraction) + (fraction >> 1)) >> shift; +} + +void handle_fade( struct Nsf_Emu* this, long out_count, sample_t* out ) +{ + int i; + for ( i = 0; i < out_count; i += fade_block_size ) + { + int const shift = 14; + int const unit = 1 << shift; + int gain = int_log( (this->out_time + i - this->fade_start) / fade_block_size, + this->fade_step, unit ); + if ( gain < (unit >> fade_shift) ) + this->track_ended = this->emu_track_ended_ = true; + + sample_t* io = &out [i]; + int count; + for ( count = min( fade_block_size, out_count - i ); count; --count ) + { + *io = (sample_t) ((*io * gain) >> shift); + ++io; + } + } +} + +// Silence detection + +void emu_play( struct Nsf_Emu* this, long count, sample_t* out ) ICODE_ATTR; +void emu_play( struct Nsf_Emu* this, long count, sample_t* out ) +{ + check( current_track_ >= 0 ); + this->emu_time += count; + if ( this->current_track >= 0 && !this->emu_track_ended_ ) { + + // End track if error + if ( play_( this, count, out ) ) + this->emu_track_ended_ = true; + } + else + memset( out, 0, count * sizeof *out ); +} + +// number of consecutive silent samples at end +static long count_silence( sample_t* begin, long size ) +{ + sample_t first = *begin; + *begin = silence_threshold; // sentinel + sample_t* p = begin + size; + while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { } + *begin = first; + return size - (p - begin); +} + +// fill internal buffer and check it for silence +void fill_buf( struct Nsf_Emu* this ) +{ + assert( !this->buf_remain ); + if ( !this->emu_track_ended_ ) + { + emu_play( this, buf_size, this->buf ); + long silence = count_silence( this->buf, buf_size ); + if ( silence < buf_size ) + { + this->silence_time = this->emu_time - silence; + this->buf_remain = buf_size; + return; + } + } + this->silence_count += buf_size; +} + +blargg_err_t Nsf_play( struct Nsf_Emu* this, long out_count, sample_t* out ) +{ + if ( this->track_ended ) + { + memset( out, 0, out_count * sizeof *out ); + } + else + { + require( this->current_track >= 0 ); + require( out_count % stereo == 0 ); + + assert( this->emu_time >= this->out_time ); + + long pos = 0; + if ( this->silence_count ) + { + // during a run of silence, run emulator at >=2x speed so it gets ahead + long ahead_time = this->silence_lookahead * (this->out_time + out_count - this->silence_time) + this->silence_time; + while ( this->emu_time < ahead_time && !(this->buf_remain | this->emu_track_ended_) ) + fill_buf( this ); + + // fill with silence + pos = min( this->silence_count, out_count ); + memset( out, 0, pos * sizeof *out ); + this->silence_count -= pos; + + if ( this->emu_time - this->silence_time > silence_max * stereo * this->sample_rate ) + { + this->track_ended = this->emu_track_ended_ = true; + this->silence_count = 0; + this->buf_remain = 0; + } + } + + if ( this->buf_remain ) + { + // empty silence buf + long n = min( this->buf_remain, out_count - pos ); + memcpy( &out [pos], this->buf + (buf_size - this->buf_remain), n * sizeof *out ); + this->buf_remain -= n; + pos += n; + } + + // generate remaining samples normally + long remain = out_count - pos; + if ( remain ) + { + emu_play( this, remain, out + pos ); + this->track_ended |= this->emu_track_ended_; + + if ( !this->ignore_silence || this->out_time > this->fade_start ) + { + // check end for a new run of silence + long silence = count_silence( out + pos, remain ); + if ( silence < remain ) + this->silence_time = this->emu_time - silence; + + if ( this->emu_time - this->silence_time >= buf_size ) + fill_buf( this ); // cause silence detection on next play() + } + } + + if ( this->out_time > this->fade_start ) + handle_fade( this, out_count, out ); + } + this->out_time += out_count; + return 0; +} + +blargg_err_t play_( struct Nsf_Emu* this, long count, sample_t* out ) +{ + long remain = count; + while ( remain ) + { + remain -= Buffer_read_samples( &this->stereo_buf, &out [count - remain], remain ); + if ( remain ) + { + if ( this->buf_changed_count != Buffer_channels_changed_count( &this->stereo_buf ) ) + { + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); + + // Remute voices + Sound_mute_voices( this, this->mute_mask_ ); + } + int msec = Buffer_length( &this->stereo_buf ); + blip_time_t clocks_emulated = (blargg_long) msec * this->clock_rate__ / 1000 - 100; + RETURN_ERR( run_clocks( this, &clocks_emulated, msec ) ); + assert( clocks_emulated ); + Buffer_end_frame( &this->stereo_buf, clocks_emulated ); + } + } + return 0; +} + +blargg_err_t run_clocks( struct Nsf_Emu* this, blip_time_t* duration, int msec ) +{ +#if defined(ROCKBOX) + (void) msec; +#endif + + end_frame( this, *duration ); + return 0; +} diff --git a/apps/codecs/libgme/nsf_emu.h b/apps/codecs/libgme/nsf_emu.h new file mode 100644 index 0000000000..421425e339 --- /dev/null +++ b/apps/codecs/libgme/nsf_emu.h @@ -0,0 +1,262 @@ +// Nintendo NES/Famicom NSF music file emulator + +// Game_Music_Emu 0.5.5 +#ifndef NSF_EMU_H +#define NSF_EMU_H + +#include "rom_data.h" +#include "multi_buffer.h" +#include "nes_apu.h" +#include "nes_cpu.h" +#include "nsfe_info.h" +#include "m3u_playlist.h" + +#ifndef NSF_EMU_APU_ONLY + #include "nes_namco_apu.h" + #include "nes_vrc6_apu.h" + #include "nes_fme7_apu.h" + #include "nes_fds_apu.h" + #include "nes_mmc5_apu.h" + #include "nes_vrc7_apu.h" +#endif + +typedef short sample_t; + +// Sound chip flags +enum { + vrc6_flag = 1 << 0, + vrc7_flag = 1 << 1, + fds_flag = 1 << 2, + mmc5_flag = 1 << 3, + namco_flag = 1 << 4, + fme7_flag = 1 << 5 +}; + +enum { fds_banks = 2 }; +enum { bank_count = fds_banks + 8 }; + +enum { rom_begin = 0x8000 }; +enum { bank_select_addr = 0x5FF8 }; +enum { mem_size = 0x10000 }; + +// cpu sits here when waiting for next call to play routine +enum { idle_addr = 0x5FF6 }; +enum { banks_addr = idle_addr }; +enum { badop_addr = bank_select_addr }; + +enum { low_ram_size = 0x800 }; +enum { sram_size = 0x2000 }; +enum { fdsram_size = 0x6000 }; +enum { fdsram_offset = 0x2000 + page_size + 8 }; +enum { sram_addr = 0x6000 }; +enum { unmapped_size= page_size + 8 }; + +enum { buf_size = 2048 }; + +// NSF file header +enum { header_size = 0x80 }; +struct header_t +{ + char tag [5]; + byte vers; + byte track_count; + byte first_track; + byte load_addr [2]; + byte init_addr [2]; + byte play_addr [2]; + char game [32]; + char author [32]; + char copyright [32]; + byte ntsc_speed [2]; + byte banks [8]; + byte pal_speed [2]; + byte speed_flags; + byte chip_flags; + byte unused [4]; +}; + +struct Nsf_Emu { + // Play routine timing + nes_time_t next_play; + nes_time_t play_period; + int play_extra; + int play_delay; + struct registers_t saved_state; // of interrupted init routine + + int track_count; + + // general + int max_initial_silence; + int voice_count; + int mute_mask_; + double tempo; + double gain; + + long sample_rate; + + // track-specific + int current_track; + blargg_long out_time; // number of samples played since start of track + blargg_long emu_time; // number of samples emulator has generated since start of track + bool emu_track_ended_; // emulator has reached end of track + volatile bool track_ended; + + // fading + blargg_long fade_start; + int fade_step; + + // silence detection + int silence_lookahead; // speed to run emulator when looking ahead for silence + bool ignore_silence; + long silence_time; // number of samples where most recent silence began + long silence_count; // number of samples of silence to play before using buf + long buf_remain; // number of samples left in silence buffer + + double clock_rate_; + long clock_rate__; + unsigned buf_changed_count; + + // M3u Playlist + struct M3u_Playlist m3u; + + // Larger items at the end + #ifndef NSF_EMU_APU_ONLY + byte mmc5_mul [2]; + + struct Nes_Fds_Apu fds; + struct Nes_Mmc5_Apu mmc5; + struct Nes_Namco_Apu namco; + struct Nes_Vrc6_Apu vrc6; + struct Nes_Fme7_Apu fme7; + struct Nes_Vrc7_Apu vrc7; + #endif + + struct Nes_Cpu cpu; + struct Nes_Apu apu; + + // Header for currently loaded file + struct header_t header; + + struct Stereo_Buffer stereo_buf; + struct Rom_Data rom; + + // Extended nsf info + struct Nsfe_Info info; + + sample_t buf [buf_size]; + byte high_ram[fdsram_size + fdsram_offset]; + byte low_ram [low_ram_size]; +}; + +// Basic functionality (see Gme_File.h for file loading/track info functions) + +void Nsf_init( struct Nsf_Emu* this ); +blargg_err_t Nsf_load( struct Nsf_Emu* this, void* data, long size ); +blargg_err_t Nsf_post_load( struct Nsf_Emu* this ); + +// Set output sample rate. Must be called only once before loading file. +blargg_err_t Nsf_set_sample_rate( struct Nsf_Emu* this, long sample_rate ); + +// Start a track, where 0 is the first track. Also clears warning string. +blargg_err_t Nsf_start_track( struct Nsf_Emu* this , int ); + +// Generate 'count' samples info 'buf'. Output is in stereo. Any emulation +// errors set warning string, and major errors also end track. +blargg_err_t Nsf_play( struct Nsf_Emu* this, long count, sample_t* buf ); + +void Nsf_clear_playlist( struct Nsf_Emu* this ); +void Nsf_disable_playlist( struct Nsf_Emu* this, bool b ); // use clear_playlist() + +// Track status/control + +// Number of milliseconds (1000 msec = 1 second) played since beginning of track +long Track_tell( struct Nsf_Emu* this ); + +// Seek to new time in track. Seeking backwards or far forward can take a while. +blargg_err_t Track_seek( struct Nsf_Emu* this, long msec ); + +// Skip n samples +blargg_err_t Track_skip( struct Nsf_Emu* this, long n ); + +// Set start time and length of track fade out. Once fade ends track_ended() returns +// true. Fade time can be changed while track is playing. +void Track_set_fade( struct Nsf_Emu* this, long start_msec, long length_msec ); + +// Get track length in milliseconds +long Track_length( struct Nsf_Emu* this, int n ); + +// Sound customization + +// Adjust song tempo, where 1.0 = normal, 0.5 = half speed, 2.0 = double speed. +// Track length as returned by track_info() assumes a tempo of 1.0. +void Sound_set_tempo( struct Nsf_Emu* this, double t ); + +// Mute/unmute voice i, where voice 0 is first voice +void Sound_mute_voice( struct Nsf_Emu* this, int index, bool mute ); + +// Set muting state of all voices at once using a bit mask, where -1 mutes them all, +// 0 unmutes them all, 0x01 mutes just the first voice, etc. +void Sound_mute_voices( struct Nsf_Emu* this, int mask ); + +// Change overall output amplitude, where 1.0 results in minimal clamping. +// Must be called before set_sample_rate(). +static inline void Sound_set_gain( struct Nsf_Emu* this, double g ) +{ + assert( !this->sample_rate ); // you must set gain before setting sample rate + this->gain = g; +} + +// Emulation (You shouldn't touch these) + +blargg_err_t run_clocks( struct Nsf_Emu* this, blip_time_t* duration, int ); + +void map_memory( struct Nsf_Emu* this ) ICODE_ATTR; +void write_bank( struct Nsf_Emu* this, int index, int data ) ICODE_ATTR; +int cpu_read( struct Nsf_Emu* this, addr_t ) ICODE_ATTR; +void cpu_write( struct Nsf_Emu* this, addr_t, int ) ICODE_ATTR; +void push_byte( struct Nsf_Emu* this, int ) ICODE_ATTR; +addr_t get_addr( byte const [] ) ICODE_ATTR; +bool run_cpu_until( struct Nsf_Emu* this, nes_time_t end ) ICODE_ATTR; + +// Sets clocks between calls to play routine to p + 1/2 clock +static inline void set_play_period( struct Nsf_Emu* this, int p ) { this->play_period = p; } + +// Time play routine will next be called +static inline nes_time_t play_time( struct Nsf_Emu* this ) { return this->next_play; } + +// Emulates to at least time t. Might emulate a few clocks extra. +void run_until( struct Nsf_Emu* this, nes_time_t t ) ICODE_ATTR; + +// Runs cpu to at least time t and returns false, or returns true +// if it encounters illegal instruction (halt). +bool run_cpu_until( struct Nsf_Emu* this, nes_time_t t ) ICODE_ATTR; + +// cpu calls through to these to access memory (except instructions) +int read_mem( struct Nsf_Emu* this, addr_t ) ICODE_ATTR; +void write_mem( struct Nsf_Emu* this, addr_t, int ) ICODE_ATTR; + +// Address of play routine +static inline addr_t play_addr( struct Nsf_Emu* this ) { return get_addr( this->header.play_addr ); } + +// Same as run_until, except emulation stops for any event (routine returned, +// play routine called, illegal instruction). +void run_once( struct Nsf_Emu* this, nes_time_t ) ICODE_ATTR; + +// Reads byte as cpu would when executing code. Only works for RAM/ROM, +// NOT I/O like sound chips. +int read_code( struct Nsf_Emu* this, addr_t addr ) ICODE_ATTR; + +static inline byte* fdsram( struct Nsf_Emu* this ) { return &this->high_ram [fdsram_offset]; } +static inline byte* sram( struct Nsf_Emu* this ) { return this->high_ram; } +static inline byte* unmapped_code( struct Nsf_Emu* this ) { return &this->high_ram [sram_size]; } + +#ifndef NSF_EMU_APU_ONLY +static inline int fds_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & fds_flag; } +static inline int vrc6_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & vrc6_flag; } +static inline int vrc7_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & vrc7_flag; } +static inline int mmc5_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & mmc5_flag; } +static inline int namco_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & namco_flag; } +static inline int fme7_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & fme7_flag; } +#endif + +#endif diff --git a/apps/codecs/libgme/nsfe_info.c b/apps/codecs/libgme/nsfe_info.c new file mode 100644 index 0000000000..d22b763173 --- /dev/null +++ b/apps/codecs/libgme/nsfe_info.c @@ -0,0 +1,272 @@ +// Game_Music_Emu 0.5.5. http://www.slack.net/~ant/ + +#include "nsf_emu.h" + +#include "blargg_endian.h" +#include + +/* Copyright (C) 2005-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void Info_init( struct Nsfe_Info* this ) +{ + this->playlist_disabled = false; +} + +void Info_unload( struct Nsfe_Info* this ) +{ + memset(this->playlist, 0, 256); + memset(this->track_times, 0, 256 * sizeof(int32_t)); + + this->playlist_size = 0; + this->track_times_size = 0; +} + +// TODO: if no playlist, treat as if there is a playlist that is just 1,2,3,4,5... ? +void Info_disable_playlist( struct Nsfe_Info* this, bool b ) +{ + this->playlist_disabled = b; + this->track_count = this->playlist_size; + if ( !this->track_count || this->playlist_disabled ) + this->track_count = this->actual_track_count_; +} + +int Info_remap_track( struct Nsfe_Info* this, int track ) +{ + if ( !this->playlist_disabled && (unsigned) track < (unsigned) this->playlist_size ) + track = this->playlist [track]; + return track; +} + +const char eof_error [] = "Unexpected end of file"; + +// Read n bytes from memory buffer +static blargg_err_t in_read( void* dst, long bytes, void* data, long* offset, long size ) +{ + if ((*offset + bytes) > size) return eof_error; + + memcpy(dst, (char*) data + *offset, bytes); + *offset += bytes; + return 0; +} + +static blargg_err_t in_skip( long bytes, long *offset, long size ) +{ + if ((*offset + bytes) > size) return eof_error; + + *offset += bytes; + return 0; +} + +// Skip n bytes from memory buffer + +// Read multiple strings and separate into individual strings +static int read_strs( void* data, long bytes, long* offset, long size, + const char* strs [4] ) +{ + char* chars = (char*) data + *offset; + chars [bytes - 1] = 0; // in case last string doesn't have terminator + + if ( in_skip( bytes, offset, size) ) + return -1; + + int count = 0, i; + for ( i = 0; i < bytes; i++ ) + { + strs [count] = &chars [i]; + while ( i < bytes && chars [i] ) + i++; + + count++; + if (count >= 4) + break; + } + + return count; +} + +struct nsfe_info_t +{ + byte load_addr [2]; + byte init_addr [2]; + byte play_addr [2]; + byte speed_flags; + byte chip_flags; + byte track_count; + byte first_track; + byte unused [6]; +}; + +blargg_err_t Info_load( struct Nsfe_Info* this, void* data, long size, struct Nsf_Emu* nsf_emu ) +{ + long offset = 0; + int const nsfe_info_size = 16; + assert( offsetof (struct nsfe_info_t,unused [6]) == nsfe_info_size ); + + // check header + byte signature [4]; + blargg_err_t err = in_read( signature, sizeof signature, data, &offset, size ); + if ( err ) + return (err == eof_error ? gme_wrong_file_type : err); + if ( memcmp( signature, "NSFE", 4 ) ) { + } + + // free previous info + /* TODO: clear track_names */ + memset(this->playlist, 0, 256); + memset(this->track_times, 0, 256 * sizeof(int32_t)); + + this->playlist_size = 0; + this->track_times_size = 0; + + // default nsf header + static const struct header_t base_header = + { + {'N','E','S','M','\x1A'},// tag + 1, // version + 1, 1, // track count, first track + {0,0},{0,0},{0,0}, // addresses + "","","", // strings + {0x1A, 0x41}, // NTSC rate + {0,0,0,0,0,0,0,0}, // banks + {0x20, 0x4E}, // PAL rate + 0, 0, // flags + {0,0,0,0} // unused + }; + + memcpy( &nsf_emu->header, &base_header, sizeof base_header ); + + // parse tags + int phase = 0; + while ( phase != 3 ) + { + // read size and tag + byte block_header [2] [4]; + RETURN_ERR( in_read( block_header, sizeof block_header, data, &offset, size ) ); + + blargg_long chunk_size = get_le32( block_header [0] ); + blargg_long tag = get_le32( block_header [1] ); + + switch ( tag ) + { + case BLARGG_4CHAR('O','F','N','I'): { + check( phase == 0 ); + if ( chunk_size < 8 ) + return "Corrupt file"; + + struct nsfe_info_t finfo; + finfo.track_count = 1; + finfo.first_track = 0; + + RETURN_ERR( in_read( &finfo, min( chunk_size, (blargg_long) nsfe_info_size ), + (char*) data, &offset, size ) ); + + if ( chunk_size > nsfe_info_size ) + RETURN_ERR( in_skip( chunk_size - nsfe_info_size, &offset, size ) ); + + phase = 1; + nsf_emu->header.speed_flags = finfo.speed_flags; + nsf_emu->header.chip_flags = finfo.chip_flags; + nsf_emu->header.track_count = finfo.track_count; + this->actual_track_count_ = finfo.track_count; + nsf_emu->header.first_track = finfo.first_track; + memcpy( nsf_emu->header.load_addr, finfo.load_addr, 2 * 3 ); + break; + } + + case BLARGG_4CHAR('K','N','A','B'): + if ( chunk_size > (int) sizeof nsf_emu->header.banks ) + return "Corrupt file"; + RETURN_ERR( in_read( nsf_emu->header.banks, chunk_size, data, &offset, size ) ); + break; + + case BLARGG_4CHAR('h','t','u','a'): { + const char* strs [4]; + int n = read_strs( data, chunk_size, &offset, size, strs ); + if ( n < 0 ) + return eof_error; + break; + } + + case BLARGG_4CHAR('e','m','i','t'): + this->track_times_size = chunk_size / 4; + RETURN_ERR( in_read( this->track_times, this->track_times_size * 4, data, &offset, size ) ); + break; + + case BLARGG_4CHAR('l','b','l','t'): + RETURN_ERR( in_skip( chunk_size, &offset, size ) ); + break; + + case BLARGG_4CHAR('t','s','l','p'): + this->playlist_size = chunk_size; + RETURN_ERR( in_read( &this->playlist [0], chunk_size, data, &offset, size ) ); + break; + + case BLARGG_4CHAR('A','T','A','D'): { + check( phase == 1 ); + phase = 2; + if ( !nsf_emu ) + { + RETURN_ERR( in_skip( chunk_size, &offset, size ) ); + } + else + { + // Avoid unexpected end of file + if ( (offset + chunk_size) > size ) + return eof_error; + + RETURN_ERR( Rom_load( &nsf_emu->rom, (char*) data + offset, chunk_size, 0, 0, 0 ) ); + RETURN_ERR( Nsf_post_load( nsf_emu ) ); + offset += chunk_size; + } + break; + } + + case BLARGG_4CHAR('D','N','E','N'): + check( phase == 2 ); + phase = 3; + break; + + default: + // tags that can be skipped start with a lowercase character + check( islower( (tag >> 24) & 0xFF ) ); + RETURN_ERR( in_skip( chunk_size, &offset, size ) ); + break; + } + } + + return 0; +} + +long Track_length( struct Nsf_Emu* this, int n ) +{ + long length = 0; + if ( (this->m3u.size > 0) && (n < this->m3u.size) ) { + struct entry_t* entry = &this->m3u.entries [n]; + length = entry->length; + } + else if ( (this->info.playlist_size > 0) && (n < this->info.playlist_size) ) { + int remapped = Info_remap_track( &this->info, n ); + if ( (unsigned) remapped < (unsigned) this->info.track_times_size ) + length = (int32_t) get_le32( &this->info.track_times [remapped] ); + } + else if( (unsigned) n < (unsigned) this->info.track_times_size ) + length = (int32_t) get_le32( &this->info.track_times [n] ); + + /* Length will be 2,30 minutes for one track songs, + and 1,45 minutes for multitrack songs */ + if ( length <= 0 ) + length = (this->track_count > 1 ? 105 : 150) * 1000; + + return length; +} diff --git a/apps/codecs/libgme/nsfe_info.h b/apps/codecs/libgme/nsfe_info.h new file mode 100644 index 0000000000..9dcde7b68a --- /dev/null +++ b/apps/codecs/libgme/nsfe_info.h @@ -0,0 +1,30 @@ +// Nintendo NES/Famicom NSFE file info parser + +// Game_Music_Emu 0.5.5 +#ifndef NSFE_INFO_H +#define NSFE_INFO_H + +#include "blargg_common.h" + +struct Nsf_Emu; + +// Allows reading info from NSFE file without creating emulator +struct Nsfe_Info { + int playlist_size; + int track_times_size; + int track_count; + int actual_track_count_; + bool playlist_disabled; + + unsigned char playlist [256]; + int32_t track_times [256]; +}; + +void Info_init( struct Nsfe_Info* this ); +blargg_err_t Info_load( struct Nsfe_Info* this, void *data, long size, struct Nsf_Emu* ); +void Info_disable_playlist( struct Nsfe_Info* this, bool b ); +int Info_remap_track( struct Nsfe_Info* this, int i ); +void Info_unload( struct Nsfe_Info* this ); + + +#endif diff --git a/apps/codecs/libgme/opl_apu.c b/apps/codecs/libgme/opl_apu.c new file mode 100644 index 0000000000..bde5e9e26e --- /dev/null +++ b/apps/codecs/libgme/opl_apu.c @@ -0,0 +1,198 @@ +#include "opl_apu.h" + +#include "blargg_source.h" + +/* NOTE: Removed unused chips ~ gama */ + +blargg_err_t Opl_init( struct Opl_Apu* this, long clock, long rate, blip_time_t period, enum opl_type_t type ) +{ + Synth_init( &this->synth ); + + this->type_ = type; + this->clock_ = clock; + this->rate_ = rate; + this->period_ = period; + Opl_set_output( this, 0 ); + Opl_volume( this, 1.0 ); + + switch (type) + { + case type_opll: + case type_msxmusic: + case type_smsfmunit: + OPLL_new ( &this->opll, clock, rate ); + OPLL_reset_patch( &this->opll, OPLL_2413_TONE ); + break; + case type_vrc7: + OPLL_new ( &this->opll, clock, rate ); + OPLL_reset_patch( &this->opll, OPLL_VRC7_TONE ); + break; + case type_msxaudio: + OPL_init( &this->opl, this->opl_memory, sizeof this->opl_memory ); + OPL_setSampleRate( &this->opl, rate, clock ); + OPL_setInternalVolume(&this->opl, 1 << 13); + break; + } + + Opl_reset( this ); + return 0; +} + +void Opl_shutdown( struct Opl_Apu* this ) +{ + switch (this->type_) + { + case type_opll: + case type_msxmusic: + case type_smsfmunit: + case type_vrc7: + OPLL_delete( &this->opll ); + break; + case type_msxaudio: break; + } +} + +void Opl_reset( struct Opl_Apu* this ) +{ + this->addr = 0; + this->next_time = 0; + this->last_amp = 0; + + switch (this->type_) + { + case type_opll: + case type_msxmusic: + case type_smsfmunit: + case type_vrc7: + OPLL_reset( &this->opll ); + OPLL_setMask( &this->opll, 0 ); + break; + case type_msxaudio: + OPL_reset( &this->opl ); + break; + } +} + +static void run_until( struct Opl_Apu* this, blip_time_t end_time ); +void Opl_write_data( struct Opl_Apu* this, blip_time_t time, int data ) +{ + run_until( this, time ); + switch (this->type_) + { + case type_opll: + case type_msxmusic: + case type_smsfmunit: + case type_vrc7: + OPLL_writeIO( &this->opll, 0, this->addr ); + OPLL_writeIO( &this->opll, 1, data ); + break; + case type_msxaudio: + OPL_writeReg( &this->opl, this->addr, data ); + break; + } +} + +int Opl_read( struct Opl_Apu* this, blip_time_t time, int port ) +{ + run_until( this, time ); + switch (this->type_) + { + case type_opll: + case type_msxmusic: + case type_smsfmunit: + case type_vrc7: + return OPLL_read( &this->opll, port ); + case type_msxaudio: + return OPL_readStatus( &this->opl ); + } + + return 0; +} + +void Opl_end_frame( struct Opl_Apu* this, blip_time_t time ) +{ + run_until( this, time ); + this->next_time -= time; + + if ( this->output_ ) + Blip_set_modified( this->output_ ); +} + +static void run_until( struct Opl_Apu* this, blip_time_t end_time ) +{ + if ( end_time > this->next_time ) + { + blip_time_t time_delta = end_time - this->next_time; + blip_time_t time = this->next_time; + unsigned count = time_delta / this->period_ + 1; + switch (this->type_) + { + case type_opll: + case type_msxmusic: + case type_smsfmunit: + case type_vrc7: + { + OPLL* opll = &this->opll; // cache + struct Blip_Buffer* const output = this->output_; + while ( count > 0 ) + { + unsigned todo = count; + if ( todo > 1024 ) todo = 1024; + short *buffer = OPLL_update_buffer(opll, todo); + + if ( output && buffer ) + { + int last_amp = this->last_amp; + unsigned i; + for ( i = 0; i < todo; i++ ) + { + int amp = buffer [i]; + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + Synth_offset_inline( &this->synth, time, delta, output ); + } + time += this->period_; + } + this->last_amp = last_amp; + } + count -= todo; + } + } + break; + case type_msxaudio: + { + struct Y8950* opl = &this->opl; + struct Blip_Buffer* const output = this->output_; + while ( count > 0 ) + { + unsigned todo = count; + if ( todo > 1024 ) todo = 1024; + int *buffer = OPL_updateBuffer(opl, todo); + + if ( output && buffer ) + { + int last_amp = this->last_amp; + unsigned i; + for ( i = 0; i < todo; i++ ) + { + int amp = buffer [i]; + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + Synth_offset_inline( &this->synth, time, delta, output ); + } + time += this->period_; + } + this->last_amp = last_amp; + } + count -= todo; + } + } + break; + } + this->next_time = time; + } +} diff --git a/apps/codecs/libgme/opl_apu.h b/apps/codecs/libgme/opl_apu.h new file mode 100644 index 0000000000..3f5a751976 --- /dev/null +++ b/apps/codecs/libgme/opl_apu.h @@ -0,0 +1,62 @@ +#ifndef OPL_APU_H +#define OPL_APU_H + +#include "blargg_common.h" +#include "blargg_source.h" +#include "blip_buffer.h" + +#include "emu8950.h" +#include "emu2413.h" + +enum opl_type_t { type_opll = 0x10, type_msxmusic = 0x11, type_smsfmunit = 0x12, + type_vrc7 = 0x13, type_msxaudio = 0x21 }; + +enum { opl_osc_count = 1 }; + +struct Opl_Apu { + struct Blip_Buffer* output_; + enum opl_type_t type_; + + blip_time_t next_time; + int last_amp; + int addr; + + long clock_; + long rate_; + blip_time_t period_; + + struct Blip_Synth synth; + + // OPL chips + struct Y8950 opl; + OPLL opll; + + unsigned char regs[ 0x100 ]; + unsigned char opl_memory[ 32768 ]; +}; + +blargg_err_t Opl_init( struct Opl_Apu* this, long clock, long rate, blip_time_t period, enum opl_type_t type ); + +void Opl_reset( struct Opl_Apu* this ); +static inline void Opl_volume( struct Opl_Apu* this, double v ) { Synth_volume( &this->synth, 1.0 / (4096 * 6) * v ); } + +static inline void Opl_osc_output( struct Opl_Apu* this, int i, struct Blip_Buffer* buf ) +{ +#if defined(ROCKBOX) + (void) i; +#endif + assert( (unsigned) i < opl_osc_count ); + this->output_ = buf; +} + +static inline void Opl_set_output( struct Opl_Apu* this, struct Blip_Buffer* buf ) { Opl_osc_output( this, 0, buf ); } +void Opl_end_frame( struct Opl_Apu* this, blip_time_t ) ICODE_ATTR; + +static inline void Opl_write_addr( struct Opl_Apu* this, int data ) { this->addr = data; } +void Opl_write_data( struct Opl_Apu* this, blip_time_t, int data ) ICODE_ATTR; + +int Opl_read( struct Opl_Apu* this, blip_time_t, int port ) ICODE_ATTR; + +static inline bool Opl_supported( void ) { return true; } + +#endif diff --git a/apps/codecs/libgme/opltables.h b/apps/codecs/libgme/opltables.h new file mode 100644 index 0000000000..1414f2264b --- /dev/null +++ b/apps/codecs/libgme/opltables.h @@ -0,0 +1,242 @@ +#ifndef _OPLTABLES_H_ +#define _OPLTABLES_H_ + +/* Precalculated emu8950 tables for use in Rockbox, + Calculated for 44Khz sampling rate */ + +static const short ar_adjust_coeff[] ICONST_ATTR = { + 255, 227, 210, 198, 189, 181, 175, 170, 165, 161, 157, + 153, 150, 147, 144, 141, 139, 136, 134, 132, 130, 128, + 126, 125, 123, 121, 120, 118, 117, 115, 114, 113, 112, + 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, + 99, 98, 97, 96, 95, 94, 94, 93, 92, 91, 91, + 90, 89, 88, 88, 87, 86, 86, 85, 84, 84, 83, + 82, 82, 81, 81, 80, 79, 79, 78, 78, 77, 77, + 76, 76, 75, 75, 74, 74, 73, 73, 72, 72, 71, + 71, 70, 70, 69, 69, 69, 68, 68, 67, 67, 66, + 66, 66, 65, 65, 64, 64, 64, 63, 63, 62, 62, + 62, 61, 61, 61, 60, 60, 60, 59, 59, 59, 58, + 58, 58, 57, 57, 57, 56, 56, 56, 55, 55, 55, + 54, 54, 54, 53, 53, 53, 53, 52, 52, 52, 51, + 51, 51, 50, 50, 50, 50, 49, 49, 49, 49, 48, + 48, 48, 48, 47, 47, 47, 46, 46, 46, 46, 45, + 45, 45, 45, 44, 44, 44, 44, 44, 43, 43, 43, + 43, 42, 42, 42, 42, 41, 41, 41, 41, 41, 40, + 40, 40, 40, 39, 39, 39, 39, 39, 38, 38, 38, + 38, 38, 37, 37, 37, 37, 37, 36, 36, 36, 36, + 36, 35, 35, 35, 35, 35, 34, 34, 34, 34, 34, + 33, 33, 33, 33, 33, 33, 32, 32, 32, 32, 32, + 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, + 30, 29, 29, 29, 29, 29, 29, 28, 28, 28, 28, + 28, 28, 27, 27, 27, 27, 27, 27, 26, 26, 26, + 26, 26, 26, 26, 25, 25, 25, 25, 25, 25, 25, + 24, 24, 24, 24, 24, 24, 23, 23, 23, 23, 23, + 23, 23, 22, 22, 22, 22, 22, 22, 22, 21, 21, + 21, 21, 21, 21, 21, 21, 20, 20, 20, 20, 20, + 20, 20, 19, 19, 19, 19, 19, 19, 19, 19, 18, + 18, 18, 18, 18, 18, 18, 18, 17, 17, 17, 17, + 17, 17, 17, 17, 16, 16, 16, 16, 16, 16, 16, + 16, 15, 15, 15, 15, 15, 15, 15, 15, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 +}; + +static const short db2lin_coeff[] ICONST_ATTR = { + 2047, 2003, 1960, 1918, 1877, 1837, 1798, 1759, 1722, 1685, 1649, + 1614, 1579, 1546, 1513, 1480, 1449, 1418, 1387, 1358, 1329, 1300, + 1273, 1245, 1219, 1193, 1167, 1142, 1118, 1094, 1071, 1048, 1025, + 1004, 982, 961, 941, 920, 901, 882, 863, 844, 826, 809, + 791, 774, 758, 742, 726, 710, 695, 680, 666, 651, 638, + 624, 611, 598, 585, 572, 560, 548, 536, 525, 514, 503, + 492, 481, 471, 461, 451, 442, 432, 423, 414, 405, 396, + 388, 380, 371, 364, 356, 348, 341, 333, 326, 319, 312, + 306, 299, 293, 287, 280, 274, 269, 263, 257, 252, 246, + 241, 236, 231, 226, 221, 216, 212, 207, 203, 198, 194, + 190, 186, 182, 178, 174, 170, 167, 163, 160, 156, 153, + 150, 147, 143, 140, 137, 134, 131, 129, 126, 123, 121, + 118, 115, 113, 111, 108, 106, 104, 101, 99, 97, 95, + 93, 91, 89, 87, 85, 83, 82, 80, 78, 76, 75, + 73, 72, 70, 69, 67, 66, 64, 63, 61, 60, 59, + 58, 56, 55, 54, 53, 52, 51, 49, 48, 47, 46, + 45, 44, 43, 42, 42, 41, 40, 39, 38, 37, 36, + 36, 35, 34, 33, 33, 32, 31, 31, 30, 29, 29, + 28, 27, 27, 26, 26, 25, 25, 24, 23, 23, 22, + 22, 21, 21, 21, 20, 20, 19, 19, 18, 18, 18, + 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, 14, + 13, 13, 13, 13, 12, 12, 12, 12, 11, 11, 11, + 11, 10, 10, 10, 10, 9, 9, 9, 9, 9, 8, + 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, + 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, + 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 +}; + +static const short sin_coeff[] ICONST_ATTR = { + 511, 235, 203, 185, 171, 161, 152, 145, 139, 134, 129, + 124, 120, 117, 113, 110, 107, 104, 102, 99, 97, 95, + 92, 90, 88, 87, 85, 83, 81, 80, 78, 77, 75, + 74, 72, 71, 70, 69, 67, 66, 65, 64, 63, 62, + 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 52, + 51, 50, 49, 48, 48, 47, 46, 45, 45, 44, 43, + 43, 42, 41, 41, 40, 39, 39, 38, 38, 37, 37, + 36, 35, 35, 34, 34, 33, 33, 32, 32, 31, 31, + 30, 30, 29, 29, 28, 28, 28, 27, 27, 26, 26, + 25, 25, 25, 24, 24, 23, 23, 23, 22, 22, 22, + 21, 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, + 18, 17, 17, 17, 16, 16, 16, 16, 15, 15, 15, + 14, 14, 14, 14, 13, 13, 13, 13, 12, 12, 12, + 12, 11, 11, 11, 11, 11, 10, 10, 10, 10, 9, + 9, 9, 9, 9, 8, 8, 8, 8, 8, 7, 7, + 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 5, + 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, + 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, +}; + +static const short pm0_coeff[] ICONST_ATTR = { + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, + 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, +}; + +static const short pm1_coeff[] ICONST_ATTR = { + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 258, 258, 258, 258, 258, 258, 258, 258, 258, 258, + 258, 258, 258, 258, 258, 258, 258, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 253, + 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, + 253, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, +}; + +static const short am0_coeff[] ICONST_ATTR = { + 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, +}; + +static const short am1_coeff[] ICONST_ATTR = { + 12, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, + 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 19, + 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 24, 24, 24, 24, 24, 24, 24, 24, 23, 23, 23, + 23, 23, 23, 22, 22, 22, 22, 22, 21, 21, 21, + 21, 20, 20, 20, 20, 19, 19, 19, 19, 18, 18, + 18, 17, 17, 17, 17, 16, 16, 16, 15, 15, 15, + 14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, + 11, 11, 10, 10, 10, 9, 9, 9, 9, 8, 8, + 8, 7, 7, 7, 7, 6, 6, 6, 5, 5, 5, + 5, 4, 4, 4, 4, 3, 3, 3, 3, 3, 2, + 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, + 5, 5, 5, 6, 6, 6, 7, 7, 7, 7, 8, + 8, 8, 9, 9, 9, 9, 10, 10, 10, 11, 11, + 11, 12, 12, +}; + +#endif diff --git a/apps/codecs/libgme/resampler.c b/apps/codecs/libgme/resampler.c new file mode 100644 index 0000000000..bcd98f68d2 --- /dev/null +++ b/apps/codecs/libgme/resampler.c @@ -0,0 +1,320 @@ +// Game_Music_Emu 0.5.5. http://www.slack.net/~ant/ + +#include "resampler.h" + +#include +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// TODO: fix this. hack since resampler holds back some output. +unsigned const resampler_extra = 34; + +enum { shift = 14 }; +int const unit = 1 << shift; + +blargg_err_t Resampler_setup( struct Resampler* this, double oversample, double rolloff, double gain ) +{ + (void) rolloff; + + this->gain_ = (int)((1 << gain_bits) * gain); + this->step = (int) ( oversample * unit + 0.5); + this->rate_ = 1.0 / unit * this->step; + return 0; +} + +blargg_err_t Resampler_reset( struct Resampler* this, int pairs ) +{ + // expand allocations a bit + Resampler_resize( this, pairs ); + this->resampler_size = this->oversamples_per_frame + (this->oversamples_per_frame >> 2); + + this->buffer_size = this->resampler_size; + this->pos = 0; + this->write_pos = 0; + return 0; +} + +void Resampler_resize( struct Resampler* this, int pairs ) +{ + int new_sample_buf_size = pairs * 2; + if ( this->sample_buf_size != new_sample_buf_size ) + { + this->sample_buf_size = new_sample_buf_size; + this->oversamples_per_frame = (int) (pairs * this->rate_) * 2 + 2; + Resampler_clear( this ); + } +} + +void mix_mono( struct Resampler* this, struct Stereo_Buffer* stereo_buf, dsample_t* out_ ) +{ + int const bass = BLIP_READER_BASS( stereo_buf->bufs [0] ); + BLIP_READER_BEGIN( sn, stereo_buf->bufs [0] ); + + int count = this->sample_buf_size >> 1; + BLIP_READER_ADJ_( sn, count ); + + typedef dsample_t stereo_dsample_t [2]; + stereo_dsample_t* BLARGG_RESTRICT out = (stereo_dsample_t*) out_ + count; + stereo_dsample_t const* BLARGG_RESTRICT in = + (stereo_dsample_t const*) this->sample_buf + count; + int offset = -count; + int const gain = this->gain_; + do + { + int s = BLIP_READER_READ_RAW( sn ) >> (blip_sample_bits - 16); + BLIP_READER_NEXT_IDX_( sn, bass, offset ); + + int l = (in [offset] [0] * gain >> gain_bits) + s; + int r = (in [offset] [1] * gain >> gain_bits) + s; + + BLIP_CLAMP( l, l ); + out [offset] [0] = (blip_sample_t) l; + + BLIP_CLAMP( r, r ); + out [offset] [1] = (blip_sample_t) r; + } + while ( ++offset ); + + BLIP_READER_END( sn, stereo_buf->bufs [0] ); +} + +void mix_stereo( struct Resampler* this, struct Stereo_Buffer* stereo_buf, dsample_t* out_ ) +{ + int const bass = BLIP_READER_BASS( stereo_buf->bufs [0] ); + BLIP_READER_BEGIN( snc, stereo_buf->bufs [0] ); + BLIP_READER_BEGIN( snl, stereo_buf->bufs [1] ); + BLIP_READER_BEGIN( snr, stereo_buf->bufs [2] ); + + int count = this->sample_buf_size >> 1; + BLIP_READER_ADJ_( snc, count ); + BLIP_READER_ADJ_( snl, count ); + BLIP_READER_ADJ_( snr, count ); + + typedef dsample_t stereo_dsample_t [2]; + stereo_dsample_t* BLARGG_RESTRICT out = (stereo_dsample_t*) out_ + count; + stereo_dsample_t const* BLARGG_RESTRICT in = + (stereo_dsample_t const*) this->sample_buf + count; + int offset = -count; + int const gain = this->gain_; + do + { + int sc = BLIP_READER_READ_RAW( snc ) >> (blip_sample_bits - 16); + int sl = BLIP_READER_READ_RAW( snl ) >> (blip_sample_bits - 16); + int sr = BLIP_READER_READ_RAW( snr ) >> (blip_sample_bits - 16); + BLIP_READER_NEXT_IDX_( snc, bass, offset ); + BLIP_READER_NEXT_IDX_( snl, bass, offset ); + BLIP_READER_NEXT_IDX_( snr, bass, offset ); + + int l = (in [offset] [0] * gain >> gain_bits) + sl + sc; + int r = (in [offset] [1] * gain >> gain_bits) + sr + sc; + + BLIP_CLAMP( l, l ); + out [offset] [0] = (blip_sample_t) l; + + BLIP_CLAMP( r, r ); + out [offset] [1] = (blip_sample_t) r; + } + while ( ++offset ); + + BLIP_READER_END( snc, stereo_buf->bufs [0] ); + BLIP_READER_END( snl, stereo_buf->bufs [1] ); + BLIP_READER_END( snr, stereo_buf->bufs [2] ); +} + +void mix_stereo_no_center( struct Resampler* this, struct Stereo_Buffer* stereo_buf, dsample_t* out_ ) +{ + int const bass = BLIP_READER_BASS( stereo_buf->bufs [0] ); + BLIP_READER_BEGIN( snl, stereo_buf->bufs [1] ); + BLIP_READER_BEGIN( snr, stereo_buf->bufs [2] ); + + int count = this->sample_buf_size >> 1; + BLIP_READER_ADJ_( snl, count ); + BLIP_READER_ADJ_( snr, count ); + + typedef dsample_t stereo_dsample_t [2]; + stereo_dsample_t* BLARGG_RESTRICT out = (stereo_dsample_t*) out_ + count; + stereo_dsample_t const* BLARGG_RESTRICT in = + (stereo_dsample_t const*) this->sample_buf + count; + int offset = -count; + int const gain = this->gain_; + do + { + int sl = BLIP_READER_READ_RAW( snl ) >> (blip_sample_bits - 16); + int sr = BLIP_READER_READ_RAW( snr ) >> (blip_sample_bits - 16); + BLIP_READER_NEXT_IDX_( snl, bass, offset ); + BLIP_READER_NEXT_IDX_( snr, bass, offset ); + + int l = (in [offset] [0] * gain >> gain_bits) + sl; + int r = (in [offset] [1] * gain >> gain_bits) + sr; + + BLIP_CLAMP( l, l ); + out [offset] [0] = (blip_sample_t) l; + + BLIP_CLAMP( r, r ); + out [offset] [1] = (blip_sample_t) r; + } + while ( ++offset ); + + BLIP_READER_END( snl, stereo_buf->bufs [1] ); + BLIP_READER_END( snr, stereo_buf->bufs [2] ); +} + +dsample_t const* resample_( struct Resampler* this, dsample_t** out_, + dsample_t const* out_end, dsample_t const in [], int in_size ) +{ + in_size -= write_offset; + if ( in_size > 0 ) + { + dsample_t* BLIP_RESTRICT out = *out_; + dsample_t const* const in_end = in + in_size; + + int const step = this->step; + int pos = this->pos; + + // TODO: IIR filter, then linear resample + // TODO: detect skipped sample, allowing merging of IIR and resample? + + do + { + #define INTERP( i, out )\ + out = (in [0 + i] * (unit - pos) + ((in [2 + i] + in [4 + i] + in [6 + i]) << shift) +\ + in [8 + i] * pos) >> (shift + 2); + + int out_0; + INTERP( 0, out_0 ) + INTERP( 1, out [0] = out_0; out [1] ) + out += stereo; + + pos += step; + in += ((unsigned) pos >> shift) * stereo; + pos &= unit - 1; + } + while ( in < in_end && out < out_end ); + + this->pos = pos; + *out_ = out; + } + return in; +} + +inline int resample_wrapper( struct Resampler* this, dsample_t out [], int* out_size, + dsample_t const in [], int in_size ) +{ + assert( Resampler_rate( this ) ); + + dsample_t* out_ = out; + int result = resample_( this, &out_, out + *out_size, in, in_size ) - in; + assert( out_ <= out + *out_size ); + assert( result <= in_size ); + + *out_size = out_ - out; + return result; +} + +int skip_input( struct Resampler* this, int count ) +{ + this->write_pos -= count; + if ( this->write_pos < 0 ) // occurs when downsampling + { + count += this->write_pos; + this->write_pos = 0; + } + memmove( this->buf, &this->buf [count], this->write_pos * sizeof this->buf [0] ); + return count; +} + +void play_frame_( struct Resampler* this, struct Stereo_Buffer* stereo_buf, dsample_t* out ) +{ + long pair_count = this->sample_buf_size >> 1; + blip_time_t blip_time = Blip_count_clocks( &stereo_buf->bufs [0], pair_count ); + int sample_count = this->oversamples_per_frame - this->write_pos + resampler_extra; + + int new_count = this->callback( this->callback_data, blip_time, sample_count, &this->buf [this->write_pos] ); + assert( new_count < resampler_size ); + + Buffer_end_frame( stereo_buf, blip_time ); + /* Blip_end_frame( &stereo_buf->bufs [0], blip_time ); */ + assert( Blip_samples_avail( &stereo_buf->bufs [0] ) == pair_count * 2 ); + + this->write_pos += new_count; + assert( (unsigned) this->write_pos <= this->buffer_size ); + + new_count = this->sample_buf_size; + if ( new_count ) + skip_input( this, resample_wrapper( this, this->sample_buf, &new_count, this->buf, this->write_pos ) ); + assert( new_count == (long) this->sample_buf_size ); + + int bufs_used = stereo_buf->stereo_added | stereo_buf->was_stereo; + if ( bufs_used <= 1 ) { + mix_mono( this, stereo_buf, out ); + Blip_remove_samples( &stereo_buf->bufs [0], pair_count ); + Blip_remove_silence( &stereo_buf->bufs [1], pair_count ); + Blip_remove_silence( &stereo_buf->bufs [2], pair_count ); + } + else if ( bufs_used & 1 ) { + mix_stereo( this, stereo_buf, out ); + Blip_remove_samples( &stereo_buf->bufs [0], pair_count ); + Blip_remove_samples( &stereo_buf->bufs [1], pair_count ); + Blip_remove_samples( &stereo_buf->bufs [2], pair_count ); + } + else { + mix_stereo_no_center( this, stereo_buf, out ); + Blip_remove_silence( &stereo_buf->bufs [0], pair_count ); + Blip_remove_samples( &stereo_buf->bufs [1], pair_count ); + Blip_remove_samples( &stereo_buf->bufs [2], pair_count ); + } + + // to do: this might miss opportunities for optimization + if ( !Blip_samples_avail( &stereo_buf->bufs [0] ) ) + { + stereo_buf->was_stereo = stereo_buf->stereo_added; + stereo_buf->stereo_added = 0; + } + + /* mix_mono( this, stereo_buf, out ); + Blip_remove_samples( &stereo_buf->bufs [0], pair_count ); */ +} + +void Resampler_play( struct Resampler* this, long count, dsample_t* out, struct Stereo_Buffer* stereo_buf ) +{ + // empty extra buffer + long remain = this->sample_buf_size - this->buf_pos; + if ( remain ) + { + if ( remain > count ) + remain = count; + count -= remain; + memcpy( out, &this->sample_buf [this->buf_pos], remain * sizeof *out ); + out += remain; + this->buf_pos += remain; + } + + // entire frames + while ( count >= (long) this->sample_buf_size ) + { + play_frame_( this, stereo_buf, out ); + out += this->sample_buf_size; + count -= this->sample_buf_size; + } + + // extra + if ( count ) + { + play_frame_( this, stereo_buf, this->sample_buf ); + this->buf_pos = count; + memcpy( out, this->sample_buf, count * sizeof *out ); + out += count; + } +} diff --git a/apps/codecs/libgme/resampler.h b/apps/codecs/libgme/resampler.h new file mode 100644 index 0000000000..f5e8c55119 --- /dev/null +++ b/apps/codecs/libgme/resampler.h @@ -0,0 +1,68 @@ +// Combination of Downsampler and Blip_Buffer mixing. Used by Sega FM emulators. + +// Game_Music_Emu 0.5.5 +#ifndef RESAMPLER_H +#define RESAMPLER_H + +#include "blargg_config.h" +#include "multi_buffer.h" + +typedef short dsample_t; + +enum { stereo = 2 }; +enum { max_buf_size = 3960 }; +enum { max_resampler_size = 5942 }; +enum { write_offset = 8 * stereo }; +enum { gain_bits = 14 }; + +struct Resampler { + int (*callback)( void*, blip_time_t, int, dsample_t* ); + void* callback_data; + + dsample_t sample_buf [max_buf_size]; + int sample_buf_size; + int oversamples_per_frame; + int buf_pos; + int resampler_size; + int gain_; + + // Internal resampler + dsample_t buf [max_resampler_size]; + int buffer_size; + + int write_pos; + double rate_; + + int pos; + int step; +}; + +static inline void Resampler_init( struct Resampler* this ) +{ + this->pos = 0; + this->write_pos = 0; + this->rate_ = 0; +} + +blargg_err_t Resampler_reset( struct Resampler* this, int max_pairs ); +void Resampler_resize( struct Resampler* this, int pairs_per_frame ); + +void Resampler_play( struct Resampler* this, long count, dsample_t* out, struct Stereo_Buffer* ) ICODE_ATTR; + +static inline void Resampler_set_callback(struct Resampler* this, int (*func)( void*, blip_time_t, int, dsample_t* ), void* user_data ) +{ + this->callback = func; + this->callback_data = user_data; +} + +blargg_err_t Resampler_setup( struct Resampler* this, double oversample, double rolloff, double gain ); + +static inline void Resampler_clear( struct Resampler* this ) +{ + this->buf_pos = this->sample_buf_size; + + this->pos = 0; + this->write_pos = 0; +} + +#endif diff --git a/apps/codecs/libgme/rom_data.c b/apps/codecs/libgme/rom_data.c new file mode 100644 index 0000000000..5fe3115130 --- /dev/null +++ b/apps/codecs/libgme/rom_data.c @@ -0,0 +1,68 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "rom_data.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include +#include "blargg_source.h" + +// Rom_Data + +blargg_err_t Rom_load( struct Rom_Data* this, const void* data, long size, + int header_size, void* header_out, int fill ) +{ + long file_offset = this->pad_size; + + this->rom_addr = 0; + this->mask = 0; + this->size = 0; + + if ( size <= header_size ) // <= because there must be data after header + return gme_wrong_file_type; + + // Read header + memcpy( header_out, data, header_size ); + + this->file_size = size - header_size; + this->file_data = (byte*) data + header_size; + + memset( this->unmapped, fill, this->rom_size ); + memcpy( &this->unmapped [file_offset], this->file_data, + this->file_size < this->pad_size ? this->file_size : this->pad_size ); + + return 0; +} + +void Rom_set_addr( struct Rom_Data* this, long addr ) +{ + this->rom_addr = addr - this->bank_size - pad_extra; + + long rounded = (addr + this->file_size + this->bank_size - 1) / this->bank_size * this->bank_size; + if ( rounded <= 0 ) + { + rounded = 0; + } + else + { + int shift = 0; + unsigned long max_addr = (unsigned long) (rounded - 1); + while ( max_addr >> shift ) + shift++; + this->mask = (1L << shift) - 1; + } + + if ( addr < 0 ) + addr = 0; + this->size = rounded; + this->rsize_ = rounded - this->rom_addr + pad_extra; +} diff --git a/apps/codecs/libgme/rom_data.h b/apps/codecs/libgme/rom_data.h new file mode 100644 index 0000000000..28b34f2a70 --- /dev/null +++ b/apps/codecs/libgme/rom_data.h @@ -0,0 +1,83 @@ +// Common aspects of emulators which use rom data + +// Game_Music_Emu 0.5.2 +#ifndef ROM_DATA_H +#define ROM_DATA_H + +#include "blargg_common.h" +#include "blargg_source.h" + +// ROM data handler, used by several Classic_Emu derivitives. Loads file data +// with padding on both sides, allowing direct use in bank mapping. The main purpose +// is to allow all file data to be loaded with only one read() call (for efficiency). + +extern const char gme_wrong_file_type []; // declared in gme.h + +enum { pad_extra = 8 }; +enum { max_bank_size = 0x4000 }; +enum { max_pad_size = max_bank_size + pad_extra }; +enum { max_rom_size = 2 * max_pad_size }; + +struct Rom_Data { + byte* file_data; + blargg_ulong file_size; + + blargg_long rom_addr; + blargg_long bank_size; + blargg_long rom_size; + blargg_ulong pad_size; + blargg_long mask; + blargg_long size; // TODO: eliminate + blargg_long rsize_; + + // Unmapped space + byte unmapped [max_rom_size]; +}; + +// Initialize rom +static inline void Rom_init( struct Rom_Data* this, blargg_long bank_size ) +{ + this->bank_size = bank_size; + this->pad_size = this->bank_size + pad_extra; + this->rom_size = 2 * this->pad_size; +} + +// Load file data, using already-loaded header 'h' if not NULL. Copy header +// from loaded file data into *out and fill unmapped bytes with 'fill'. +blargg_err_t Rom_load( struct Rom_Data* this, const void* data, long size, int header_size, void* header_out, int fill ); + +// Set address that file data should start at +void Rom_set_addr( struct Rom_Data* this, long addr ); + +// Mask address to nearest power of two greater than size() +static inline blargg_long mask_addr( blargg_long addr, blargg_long mask ) +{ + #ifdef check + check( addr <= mask ); + #endif + return addr & mask; +} + +// Pointer to page starting at addr. Returns unmapped() if outside data. +static inline byte* Rom_at_addr( struct Rom_Data* this, blargg_long addr ) +{ + blargg_ulong offset = mask_addr( addr, this->mask ) - this->rom_addr; + if ( offset > (blargg_ulong) (this->rsize_ - this->pad_size) ) + offset = 0; // unmapped + + if ( offset < this->pad_size ) return &this->unmapped [offset]; + else return &this->file_data [offset - this->pad_size]; +} + + +#ifndef GME_APU_HOOK + #define GME_APU_HOOK( emu, addr, data ) ((void) 0) +#endif + +#ifndef GME_FRAME_HOOK + #define GME_FRAME_HOOK( emu ) ((void) 0) +#else + #define GME_FRAME_HOOK_DEFINED 1 +#endif + +#endif diff --git a/apps/codecs/libgme/sgc_cpu.c b/apps/codecs/libgme/sgc_cpu.c new file mode 100644 index 0000000000..3bd2d15df9 --- /dev/null +++ b/apps/codecs/libgme/sgc_cpu.c @@ -0,0 +1,36 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "sgc_emu.h" + +#include "blargg_endian.h" +//#include "z80_cpu_log.h" + +/* Copyright (C) 2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#define OUT_PORT( addr, data ) cpu_out( this, TIME(), addr, data ) +#define IN_PORT( addr ) 0 // cpu in +#define WRITE_MEM( addr, data ) cpu_write( this, addr, data ) +#define IDLE_ADDR this->idle_addr +#define RST_BASE this->vectors_addr + +#define CPU_BEGIN \ +bool run_cpu( struct Sgc_Emu* this, cpu_time_t end_time )\ +{\ + Sgc_Cpu* cpu = &this->cpu; \ + Z80_set_end_time( cpu, end_time ); + + #include "z80_cpu_run.h" + + return warning; +} diff --git a/apps/codecs/libgme/sgc_emu.c b/apps/codecs/libgme/sgc_emu.c new file mode 100644 index 0000000000..9abfc00d2c --- /dev/null +++ b/apps/codecs/libgme/sgc_emu.c @@ -0,0 +1,673 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "sgc_emu.h" + +/* Copyright (C) 2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const osc_count = sms_osc_count + fm_apu_osc_count; + +int const stereo = 2; // number of channels for stereo +int const silence_max = 6; // seconds +int const silence_threshold = 0x10; +long const fade_block_size = 512; +int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) + +const char gme_wrong_file_type [] = "Wrong file type for this emulator"; + +void clear_track_vars( struct Sgc_Emu* this ) +{ + this->current_track = -1; + this->out_time = 0; + this->emu_time = 0; + this->emu_track_ended_ = true; + this->track_ended = true; + this->fade_start = INT_MAX / 2 + 1; + this->fade_step = 1; + this->silence_time = 0; + this->silence_count = 0; + this->buf_remain = 0; + /* warning(); // clear warning */ +} + +void Sgc_init( struct Sgc_Emu* this ) +{ + assert( offsetof (struct header_t,copyright [32]) == header_size ); + + this->sample_rate = 0; + this->mute_mask_ = 0; + this->tempo = 1.0; + this->gain = 1.0; + this->voice_count = 0; + + // defaults + this->max_initial_silence = 2; + this->silence_lookahead = 6; + this->ignore_silence = false; + + Sms_apu_init( &this->apu ); + Fm_apu_create( &this->fm_apu ); + + Rom_init( &this->rom, 0x4000 ); + Z80_init( &this->cpu ); + + Sound_set_gain( this, 1.2 ); + + // Unload + clear_track_vars( this ); +} + +// Setup + +blargg_err_t Sgc_load_mem( struct Sgc_Emu* this, const void* data, long size ) +{ + RETURN_ERR( Rom_load( &this->rom, data, size, header_size, &this->header, 0 ) ); + + if ( !valid_tag( &this->header ) ) + return gme_wrong_file_type; + + /* if ( header.vers != 1 ) + warning( "Unknown file version" ); */ + + /* if ( header.system > 2 ) + warning( "Unknown system" ); */ + + addr_t load_addr = get_le16( this->header.load_addr ); + /* if ( load_addr < 0x400 ) + set_warning( "Invalid load address" ); */ + + Rom_set_addr( &this->rom, load_addr ); + this->play_period = clock_rate( this ) / 60; + + if ( sega_mapping( this ) && Fm_apu_supported() ) + RETURN_ERR( Fm_apu_init( &this->fm_apu, clock_rate( this ), clock_rate( this ) / 72 ) ); + + this->m3u.size = 0; + this->track_count = this->header.song_count; + this->voice_count = sega_mapping( this ) ? osc_count : sms_osc_count; + + Sms_apu_volume( &this->apu, this->gain ); + Fm_apu_volume( &this->fm_apu, this->gain ); + + // Setup buffer + this->clock_rate_ = clock_rate( this ); + Buffer_clock_rate( &this->stereo_buf, clock_rate( this ) ); + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); + Sound_set_tempo( this, this->tempo ); + + // Remute voices + Sound_mute_voices( this, this->mute_mask_ ); + return 0; +} + +void Sound_set_voice( struct Sgc_Emu* this, int i, struct Blip_Buffer* c, struct Blip_Buffer* l, struct Blip_Buffer* r ) +{ + if ( i < sms_osc_count ) + Sms_apu_set_output( &this->apu, i, c, l, r ); + else + Fm_apu_set_output( &this->fm_apu, c ); +} + +blargg_err_t run_clocks( struct Sgc_Emu* this, blip_time_t* duration, int msec ) +{ +#if defined(ROCKBOX) + (void) msec; +#endif + + cpu_time_t t = *duration; + while ( Z80_time( &this->cpu ) < t ) + { + cpu_time_t next = min( t, this->next_play ); + if ( run_cpu( this, next ) ) + { + /* warning( "Unsupported CPU instruction" ); */ + Z80_set_time( &this->cpu, next ); + } + + if ( this->cpu.r.pc == this->idle_addr ) + Z80_set_time( &this->cpu, next ); + + if ( Z80_time( &this->cpu ) >= this->next_play ) + { + this->next_play += this->play_period; + if ( this->cpu.r.pc == this->idle_addr ) + jsr( this, this->header.play_addr ); + } + } + + this->next_play -= t; + check( this->next_play >= 0 ); + Z80_adjust_time( &this->cpu, -t ); + + Sms_apu_end_frame( &this->apu, t ); + if ( sega_mapping( this ) && this->fm_accessed ) + { + if ( Fm_apu_supported() ) + Fm_apu_end_frame( &this->fm_apu, t ); + /* else + warning( "FM sound not supported" ); */ + } + + return 0; +} + +// Emulation + +void cpu_out( struct Sgc_Emu* this, cpu_time_t time, addr_t addr, int data ) +{ + int port = addr & 0xFF; + + if ( sega_mapping( this ) ) + { + switch ( port ) + { + case 0x06: + Sms_apu_write_ggstereo( &this->apu, time, data ); + return; + + case 0x7E: + case 0x7F: + Sms_apu_write_data( &this->apu, time, data ); /* dprintf( "$7E<-%02X\n", data ); */ + return; + + case 0xF0: + this->fm_accessed = true; + if ( Fm_apu_supported() ) + Fm_apu_write_addr( &this->fm_apu, data );//, dprintf( "$F0<-%02X\n", data ); + return; + + case 0xF1: + this->fm_accessed = true; + if ( Fm_apu_supported() ) + Fm_apu_write_data( &this->fm_apu, time, data );//, dprintf( "$F1<-%02X\n", data ); + return; + } + } + else if ( port >= 0xE0 ) + { + Sms_apu_write_data( &this->apu, time, data ); + return; + } +} + +void jsr( struct Sgc_Emu* this, byte addr [2] ) +{ + *Z80_write( &this->cpu, --this->cpu.r.sp ) = this->idle_addr >> 8; + *Z80_write( &this->cpu, --this->cpu.r.sp ) = this->idle_addr & 0xFF; + this->cpu.r.pc = get_le16( addr ); +} + +void set_bank( struct Sgc_Emu* this, int bank, void const* data ) +{ + //dprintf( "map bank %d to %p\n", bank, (byte*) data - rom.at_addr( 0 ) ); + Z80_map_mem( &this->cpu, bank * this->rom.bank_size, this->rom.bank_size, this->unmapped_write, data ); +} + +void cpu_write( struct Sgc_Emu* this, addr_t addr, int data ) +{ + if ( (addr ^ 0xFFFC) > 3 || !sega_mapping( this ) ) + { + *Z80_write( &this->cpu, addr ) = data; + return; + } + + switch ( addr ) + { + case 0xFFFC: + Z80_map_mem_rw( &this->cpu, 2 * this->rom.bank_size, this->rom.bank_size, this->ram2 ); + if ( data & 0x08 ) + break; + + this->bank2 = this->ram2; + // FALL THROUGH + + case 0xFFFF: { + bool rom_mapped = (Z80_read( &this->cpu, 2 * this->rom.bank_size ) == this->bank2); + this->bank2 = Rom_at_addr( &this->rom, data * this->rom.bank_size ); + if ( rom_mapped ) + set_bank( this, 2, this->bank2 ); + break; + } + + case 0xFFFD: + set_bank( this, 0, Rom_at_addr( &this->rom, data * this->rom.bank_size ) ); + break; + + case 0xFFFE: + set_bank( this, 1, Rom_at_addr( &this->rom, data * this->rom.bank_size ) ); + break; + } +} + +blargg_err_t Sgc_set_sample_rate( struct Sgc_Emu* this, long rate ) +{ + require( !this->sample_rate ); // sample rate can't be changed once set + Buffer_init( &this->stereo_buf ); + Buffer_set_sample_rate( &this->stereo_buf, rate, 1000 / 20 ); + + // Set buffer bass + Buffer_bass_freq( &this->stereo_buf, 80 ); + + this->sample_rate = rate; + return 0; +} + +void Sound_mute_voice( struct Sgc_Emu* this, int index, bool mute ) +{ + require( (unsigned) index < (unsigned) this->voice_count ); + int bit = 1 << index; + int mask = this->mute_mask_ | bit; + if ( !mute ) + mask ^= bit; + Sound_mute_voices( this, mask ); +} + +void Sound_mute_voices( struct Sgc_Emu* this, int mask ) +{ + require( this->sample_rate ); // sample rate must be set first + this->mute_mask_ = mask; + + int i; + for ( i = this->voice_count; i--; ) + { + if ( mask & (1 << i) ) + { + Sound_set_voice( this, i, 0, 0, 0 ); + } + else + { + struct channel_t ch = Buffer_channel( &this->stereo_buf ); + assert( (ch.center && ch.left && ch.right) || + (!ch.center && !ch.left && !ch.right) ); // all or nothing + Sound_set_voice( this, i, ch.center, ch.left, ch.right ); + } + } +} + +void Sound_set_tempo( struct Sgc_Emu* this, double t ) +{ + require( this->sample_rate ); // sample rate must be set first + double const min = 0.02; + double const max = 4.00; + if ( t < min ) t = min; + if ( t > max ) t = max; + this->tempo = t; + + this->play_period = (int) (clock_rate( this ) / (this->header.rate ? 50 : 60) / t); +} + +void fill_buf( struct Sgc_Emu* this ) ICODE_ATTR; +blargg_err_t Sgc_start_track( struct Sgc_Emu* this, int track ) +{ + clear_track_vars( this ); + + // Remap track if playlist available + if ( this->m3u.size > 0 ) { + struct entry_t* e = &this->m3u.entries[track]; + track = e->track; + } + + this->current_track = track; + + if ( sega_mapping( this ) ) + { + Sms_apu_reset( &this->apu, 0, 0 ); + Fm_apu_reset( &this->fm_apu ); + this->fm_accessed = false; + } + else + { + Sms_apu_reset( &this->apu, 0x0003, 15 ); + } + + memset( this->ram , 0, sizeof this->ram ); + memset( this->ram2, 0, sizeof this->ram2 ); + memset( this->vectors, 0xFF, sizeof this->vectors ); + Z80_reset( &this->cpu, this->unmapped_write, this->rom.unmapped ); + + if ( sega_mapping( this ) ) + { + this->vectors_addr = 0x10000 - page_size; + this->idle_addr = this->vectors_addr; + int i; + for ( i = 1; i < 8; ++i ) + { + this->vectors [i*8 + 0] = 0xC3; // JP addr + this->vectors [i*8 + 1] = this->header.rst_addrs [i - 1] & 0xff; + this->vectors [i*8 + 2] = this->header.rst_addrs [i - 1] >> 8; + } + + Z80_map_mem_rw( &this->cpu, 0xC000, 0x2000, this->ram ); + Z80_map_mem( &this->cpu, this->vectors_addr, page_size, this->unmapped_write, this->vectors ); + + this->bank2 = NULL; + for ( i = 0; i < 4; ++i ) + cpu_write( this, 0xFFFC + i, this->header.mapping [i] ); + } + else + { + if ( !this->coleco_bios ) + return "Coleco BIOS not set"; /* BLARGG_ERR( BLARGG_ERR_CALLER, "Coleco BIOS not set" ); */ + + this->vectors_addr = 0; + Z80_map_mem( &this->cpu, 0, 0x2000, this->unmapped_write, this->coleco_bios ); + int i; + for ( i = 0; i < 8; ++i ) + Z80_map_mem_rw( &this->cpu, 0x6000 + i*0x400, 0x400, this->ram ); + + this->idle_addr = 0x2000; + Z80_map_mem( &this->cpu, 0x2000, page_size, this->unmapped_write, this->vectors ); + + for ( i = 0; i < 0x8000 / this->rom.bank_size; ++i ) + { + int addr = 0x8000 + i*this->rom.bank_size; + Z80_map_mem( &this->cpu, addr, this->rom.bank_size, this->unmapped_write, Rom_at_addr( &this->rom, addr ) ); + } + } + + this->cpu.r.sp = get_le16( this->header.stack_ptr ); + this->cpu.r.b.a = track; + this->next_play = this->play_period; + + jsr( this, this->header.init_addr ); + + Buffer_clear( &this->stereo_buf ); + + this->emu_track_ended_ = false; + this->track_ended = false; + + if ( !this->ignore_silence ) + { + // play until non-silence or end of track + long end; + for ( end = this->max_initial_silence * stereo * this->sample_rate; this->emu_time < end; ) + { + fill_buf( this ); + if ( this->buf_remain | (int) this->emu_track_ended_ ) + break; + } + + this->emu_time = this->buf_remain; + this->out_time = 0; + this->silence_time = 0; + this->silence_count = 0; + } + /* return track_ended() ? warning() : 0; */ + return 0; +} + +// Tell/Seek + +blargg_long msec_to_samples( blargg_long msec, long sample_rate ) +{ + blargg_long sec = msec / 1000; + msec -= sec * 1000; + return (sec * sample_rate + msec * sample_rate / 1000) * stereo; +} + +long Track_tell( struct Sgc_Emu* this ) +{ + blargg_long rate = this->sample_rate * stereo; + blargg_long sec = this->out_time / rate; + return sec * 1000 + (this->out_time - sec * rate) * 1000 / rate; +} + +blargg_err_t Track_seek( struct Sgc_Emu* this, long msec ) +{ + blargg_long time = msec_to_samples( msec, this->sample_rate ); + if ( time < this->out_time ) + RETURN_ERR( Sgc_start_track( this, this->current_track ) ); + return Track_skip( this, time - this->out_time ); +} + +blargg_err_t skip_( struct Sgc_Emu* this, long count ) ICODE_ATTR; +blargg_err_t Track_skip( struct Sgc_Emu* this, long count ) +{ + require( this->current_track >= 0 ); // start_track() must have been called already + this->out_time += count; + + // remove from silence and buf first + { + long n = min( count, this->silence_count ); + this->silence_count -= n; + count -= n; + + n = min( count, this->buf_remain ); + this->buf_remain -= n; + count -= n; + } + + if ( count && !this->emu_track_ended_ ) + { + this->emu_time += count; + + // End track if error + if ( skip_( this, count ) ) { + this->emu_track_ended_ = true; + } + } + + if ( !(this->silence_count | this->buf_remain) ) // caught up to emulator, so update track ended + this->track_ended |= this->emu_track_ended_; + + return 0; +} + +blargg_err_t play_( struct Sgc_Emu* this, long count, sample_t* out ) ICODE_ATTR; +blargg_err_t skip_( struct Sgc_Emu* this, long count ) +{ + // for long skip, mute sound + const long threshold = 30000; + if ( count > threshold ) + { + int saved_mute = this->mute_mask_; + Sound_mute_voices( this, ~0 ); + + while ( count > threshold / 2 && !this->emu_track_ended_ ) + { + RETURN_ERR( play_( this, buf_size, this->buf ) ); + count -= buf_size; + } + + Sound_mute_voices( this, saved_mute ); + } + + while ( count && !this->emu_track_ended_ ) + { + long n = buf_size; + if ( n > count ) + n = count; + count -= n; + RETURN_ERR( play_( this, n, this->buf ) ); + } + return 0; +} + +// Fading + +void Track_set_fade( struct Sgc_Emu* this, long start_msec, long length_msec ) +{ + this->fade_step = this->sample_rate * length_msec / (fade_block_size * fade_shift * 1000 / stereo); + this->fade_start = msec_to_samples( start_msec, this->sample_rate ); +} + +// unit / pow( 2.0, (double) x / step ) +static int int_log( blargg_long x, int step, int unit ) +{ + int shift = x / step; + int fraction = (x - shift * step) * unit / step; + return ((unit - fraction) + (fraction >> 1)) >> shift; +} + +void handle_fade( struct Sgc_Emu* this, long out_count, sample_t* out ) +{ + int i; + for ( i = 0; i < out_count; i += fade_block_size ) + { + int const shift = 14; + int const unit = 1 << shift; + int gain = int_log( (this->out_time + i - this->fade_start) / fade_block_size, + this->fade_step, unit ); + if ( gain < (unit >> fade_shift) ) + this->track_ended = this->emu_track_ended_ = true; + + sample_t* io = &out [i]; + int count; + for ( count = min( fade_block_size, out_count - i ); count; --count ) + { + *io = (sample_t) ((*io * gain) >> shift); + ++io; + } + } +} + +// Silence detection + +void emu_play( struct Sgc_Emu* this, long count, sample_t* out ) +{ + check( this->current_track_ >= 0 ); + this->emu_time += count; + if ( this->current_track >= 0 && !this->emu_track_ended_ ) { + // End track if error + if ( play_( this, count, out ) ) + this->emu_track_ended_ = true; + } + else + memset( out, 0, count * sizeof *out ); +} + +// number of consecutive silent samples at end +static long count_silence( sample_t* begin, long size ) +{ + sample_t first = *begin; + *begin = silence_threshold; // sentinel + sample_t* p = begin + size; + while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { } + *begin = first; + return size - (p - begin); +} + +// fill internal buffer and check it for silence +void fill_buf( struct Sgc_Emu* this ) +{ + assert( !this->buf_remain ); + if ( !this->emu_track_ended_ ) + { + emu_play( this, buf_size, this->buf ); + long silence = count_silence( this->buf, buf_size ); + if ( silence < buf_size ) + { + this->silence_time = this->emu_time - silence; + this->buf_remain = buf_size; + return; + } + } + this->silence_count += buf_size; +} + +blargg_err_t Sgc_play( struct Sgc_Emu* this, long out_count, sample_t* out ) +{ + if ( this->track_ended ) + { + memset( out, 0, out_count * sizeof *out ); + } + else + { + require( this->current_track >= 0 ); + require( out_count % stereo == 0 ); + + assert( this->emu_time >= this->out_time ); + + // prints nifty graph of how far ahead we are when searching for silence + //debug_printf( "%*s \n", int ((emu_time - out_time) * 7 / sample_rate()), "*" ); + + long pos = 0; + if ( this->silence_count ) + { + // during a run of silence, run emulator at >=2x speed so it gets ahead + long ahead_time = this->silence_lookahead * (this->out_time + out_count - this->silence_time) + this->silence_time; + while ( this->emu_time < ahead_time && !(this->buf_remain | this->emu_track_ended_) ) + fill_buf( this ); + + // fill with silence + pos = min( this->silence_count, out_count ); + memset( out, 0, pos * sizeof *out ); + this->silence_count -= pos; + + if ( this->emu_time - this->silence_time > silence_max * stereo * this->sample_rate ) + { + this->track_ended = this->emu_track_ended_ = true; + this->silence_count = 0; + this->buf_remain = 0; + } + } + + if ( this->buf_remain ) + { + // empty silence buf + long n = min( this->buf_remain, out_count - pos ); + memcpy( &out [pos], this->buf + (buf_size - this->buf_remain), n * sizeof *out ); + this->buf_remain -= n; + pos += n; + } + + // generate remaining samples normally + long remain = out_count - pos; + if ( remain ) + { + emu_play( this, remain, out + pos ); + this->track_ended |= this->emu_track_ended_; + + if ( !this->ignore_silence || this->out_time > this->fade_start ) + { + // check end for a new run of silence + long silence = count_silence( out + pos, remain ); + if ( silence < remain ) + this->silence_time = this->emu_time - silence; + + if ( this->emu_time - this->silence_time >= buf_size ) + fill_buf( this ); // cause silence detection on next play() + } + } + + if ( this->out_time > this->fade_start ) + handle_fade( this, out_count, out ); + } + this->out_time += out_count; + return 0; +} + +blargg_err_t play_( struct Sgc_Emu* this, long count, sample_t* out ) +{ + long remain = count; + while ( remain ) + { + remain -= Buffer_read_samples( &this->stereo_buf, &out [count - remain], remain ); + if ( remain ) + { + if ( this->buf_changed_count != Buffer_channels_changed_count( &this->stereo_buf ) ) + { + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); + + // Remute voices + Sound_mute_voices( this, this->mute_mask_ ); + } + int msec = Buffer_length( &this->stereo_buf ); + blip_time_t clocks_emulated = msec * this->clock_rate_ / 1000 - 100; + RETURN_ERR( run_clocks( this, &clocks_emulated, msec ) ); + assert( clocks_emulated ); + Buffer_end_frame( &this->stereo_buf, clocks_emulated ); + } + } + return 0; +} diff --git a/apps/codecs/libgme/sgc_emu.h b/apps/codecs/libgme/sgc_emu.h new file mode 100644 index 0000000000..957e7438ef --- /dev/null +++ b/apps/codecs/libgme/sgc_emu.h @@ -0,0 +1,199 @@ +// Sega/Game Gear/Coleco SGC music file emulator + +// Game_Music_Emu 0.6-pre +#ifndef SGC_EMU_H +#define SGC_EMU_H + +#include "blargg_common.h" +#include "multi_buffer.h" + +#include "rom_data.h" +#include "z80_cpu.h" +#include "sms_fm_apu.h" +#include "sms_apu.h" +#include "m3u_playlist.h" + +typedef short sample_t; +typedef struct Z80_Cpu Sgc_Cpu; + +enum { buf_size = 2048 }; + +// SGC file header +enum { header_size = 0xA0 }; +struct header_t +{ + char tag [4]; // "SGC\x1A" + byte vers; // 0x01 + byte rate; // 0=NTSC 1=PAL + byte reserved1 [2]; + byte load_addr [2]; + byte init_addr [2]; + byte play_addr [2]; + byte stack_ptr [2]; + byte reserved2 [2]; + byte rst_addrs [7*2]; + byte mapping [4]; // Used by Sega only + byte first_song; // Song to start playing first + byte song_count; + byte first_effect; + byte last_effect; + byte system; // 0=Master System 1=Game Gear 2=Colecovision + byte reserved3 [23]; + char game [32]; // strings can be 32 chars, NOT terminated + char author [32]; + char copyright [32]; +}; + +// True if header has valid file signature +static inline bool valid_tag( struct header_t* h ) +{ + return 0 == memcmp( h->tag, "SGC\x1A", 4 ); +} + +static inline int effect_count( struct header_t* h ) { return h->last_effect ? h->last_effect - h->first_effect + 1 : 0; } + + +struct Sgc_Emu { + bool fm_accessed; + + cpu_time_t play_period; + cpu_time_t next_play; + void const* bank2; // ROM selected for bank 2, in case RAM is currently hiding it + addr_t vectors_addr; // RST vectors start here + addr_t idle_addr; // return address for init/play routines + void* coleco_bios; + + // general + int voice_count; + int mute_mask_; + double tempo; + double gain; + + long sample_rate; + + // track-specific + volatile bool track_ended; + int current_track; + int track_count; + blargg_long out_time; // number of samples played since start of track + blargg_long emu_time; // number of samples emulator has generated since start of track + bool emu_track_ended_; // emulator has reached end of track + + // fading + blargg_long fade_start; + int fade_step; + + // silence detection + bool ignore_silence; + int max_initial_silence; + int silence_lookahead; // speed to run emulator when looking ahead for silence + long silence_time; // number of samples where most recent silence began + long silence_count; // number of samples of silence to play before using buf + long buf_remain; // number of samples left in silence buffer + + long clock_rate_; + unsigned buf_changed_count; + + // M3u Playlist + struct M3u_Playlist m3u; + + sample_t buf [buf_size]; + struct Stereo_Buffer stereo_buf; + + struct Sms_Apu apu; + struct Sms_Fm_Apu fm_apu; + + Sgc_Cpu cpu; + + // large items + struct header_t header; + struct Rom_Data rom; + byte vectors [page_size + page_padding]; + byte ram [0x2000 + page_padding]; + byte ram2 [0x4000 + page_padding]; + byte unmapped_write [0x4000]; +}; + +// Basic functionality (see Gme_File.h for file loading/track info functions) + +void Sgc_init( struct Sgc_Emu* this ); + +blargg_err_t Sgc_load_mem( struct Sgc_Emu* this, const void* data, long size ); + +static inline int clock_rate( struct Sgc_Emu* this ) { return this->header.rate ? 3546893 : 3579545; } + +// 0x2000 bytes +static inline void set_coleco_bios( struct Sgc_Emu* this, void* p ) { this->coleco_bios = p; } + +// Set output sample rate. Must be called only once before loading file. +blargg_err_t Sgc_set_sample_rate( struct Sgc_Emu* this, long sample_rate ); + +// Start a track, where 0 is the first track. Also clears warning string. +blargg_err_t Sgc_start_track( struct Sgc_Emu* this, int track ); + +// Generate 'count' samples info 'buf'. Output is in stereo. Any emulation +// errors set warning string, and major errors also end track. +blargg_err_t Sgc_play( struct Sgc_Emu* this, long count, sample_t* buf ); + +// Track status/control + +// Number of milliseconds (1000 msec = 1 second) played since beginning of track +long Track_tell( struct Sgc_Emu* this ); + +// Seek to new time in track. Seeking backwards or far forward can take a while. +blargg_err_t Track_seek( struct Sgc_Emu* this, long msec ); + +// Skip n samples +blargg_err_t Track_skip( struct Sgc_Emu* this, long n ); + +// Set start time and length of track fade out. Once fade ends track_ended() returns +// true. Fade time can be changed while track is playing. +void Track_set_fade( struct Sgc_Emu* this, long start_msec, long length_msec ); + +// Get track length in milliseconds +static inline long Track_get_length( struct Sgc_Emu* this, int n ) +{ + long length = 120 * 1000; /* 2 minutes */ + if ( (this->m3u.size > 0) && (n < this->m3u.size) ) { + struct entry_t* entry = &this->m3u.entries [n]; + length = entry->length; + } + + return length; +} + +// Sound customization + +// Adjust song tempo, where 1.0 = normal, 0.5 = half speed, 2.0 = double speed. +// Track length as returned by track_info() assumes a tempo of 1.0. +void Sound_set_tempo( struct Sgc_Emu* this, double t ); + +// Mute/unmute voice i, where voice 0 is first voice +void Sound_mute_voice( struct Sgc_Emu* this, int index, bool mute ); + +// Set muting state of all voices at once using a bit mask, where -1 mutes them all, +// 0 unmutes them all, 0x01 mutes just the first voice, etc. +void Sound_mute_voices( struct Sgc_Emu* this, int mask ); + +// Change overall output amplitude, where 1.0 results in minimal clamping. +// Must be called before set_sample_rate(). +static inline void Sound_set_gain( struct Sgc_Emu* this, double g ) +{ + assert( !this->sample_rate ); // you must set gain before setting sample rate + this->gain = g; +} + +// True if Master System or Game Gear +static inline bool sega_mapping( struct Sgc_Emu* this ) +{ + return this->header.system <= 1; +} + +// Emulation (You shouldn't touch these) + +bool run_cpu( struct Sgc_Emu* this, cpu_time_t end_time ) ICODE_ATTR; +void cpu_out( struct Sgc_Emu* this, cpu_time_t time, addr_t addr, int data ) ICODE_ATTR; +void cpu_write( struct Sgc_Emu* this, addr_t addr, int data ) ICODE_ATTR; +void jsr( struct Sgc_Emu* this, byte addr [2] ) ICODE_ATTR; + +#endif diff --git a/apps/codecs/libgme/sms_apu.c b/apps/codecs/libgme/sms_apu.c new file mode 100644 index 0000000000..4be63db073 --- /dev/null +++ b/apps/codecs/libgme/sms_apu.c @@ -0,0 +1,310 @@ +// Sms_Snd_Emu 0.1.1. http://www.slack.net/~ant/ + +#include "sms_apu.h" + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const noise_osc = 3; + +void Sms_apu_volume( struct Sms_Apu* this, double vol ) +{ + vol *= 0.85 / sms_osc_count / 64; + Synth_volume( &this->synth, vol ); +} + +inline int calc_output( struct Sms_Apu* this, int i ) +{ + int flags = this->ggstereo >> i; + return (flags >> 3 & 2) | (flags & 1); +} + +void Sms_apu_set_output( struct Sms_Apu* this, int i, struct Blip_Buffer* center, struct Blip_Buffer* left, struct Blip_Buffer* right ) +{ +#if defined(ROCKBOX) + (void) left; + (void) right; +#endif + + // Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL) + require( !center || (center && !left && !right) || (center && left && right) ); + require( (unsigned) i < sms_osc_count ); // fails if you pass invalid osc index + + if ( center ) + { + unsigned const divisor = 16384 * 16 * 2; + this->min_tone_period = ((unsigned) Blip_clock_rate( center ) + divisor/2) / divisor; + } + + if ( !center || !left || !right ) + { + left = center; + right = center; + } + + struct Osc* o = &this->oscs [i]; + o->outputs [0] = NULL; + o->outputs [1] = right; + o->outputs [2] = left; + o->outputs [3] = center; + o->output = o->outputs [calc_output( this, i )]; +} + +static inline unsigned fibonacci_to_galois_lfsr( unsigned fibonacci, int width ) +{ + unsigned galois = 0; + while ( --width >= 0 ) + { + galois = (galois << 1) | (fibonacci & 1); + fibonacci >>= 1; + } + return galois; +} + +void Sms_apu_reset( struct Sms_Apu* this, unsigned feedback, int noise_width ) +{ + this->last_time = 0; + this->latch = 0; + this->ggstereo = 0; + + // Calculate noise feedback values + if ( !feedback || !noise_width ) + { + feedback = 0x0009; + noise_width = 16; + } + this->looped_feedback = 1 << (noise_width - 1); + this->noise_feedback = fibonacci_to_galois_lfsr( feedback, noise_width ); + + // Reset oscs + int i; + for ( i = sms_osc_count; --i >= 0; ) + { + struct Osc* o = &this->oscs [i]; + o->output = NULL; + o->last_amp = 0; + o->delay = 0; + o->phase = 0; + o->period = 0; + o->volume = 15; // silent + } + + this->oscs [noise_osc].phase = 0x8000; + Sms_apu_write_ggstereo( this, 0, 0xFF ); +} + +void Sms_apu_init( struct Sms_Apu* this ) +{ + this->min_tone_period = 7; + + Synth_init( &this->synth ); + + // Clear outputs to NULL FIRST + this->ggstereo = 0; + + int i; + for ( i = sms_osc_count; --i >= 0; ) + Sms_apu_set_output( this, i, NULL, NULL, NULL ); + + Sms_apu_volume( this, 1.0 ); + Sms_apu_reset( this, 0, 0 ); +} + +static void run_until( struct Sms_Apu* this, blip_time_t end_time ) +{ + require( end_time >= this->last_time ); + if ( end_time <= this->last_time ) + return; + + // Synthesize each oscillator + int idx; + for ( idx = sms_osc_count; --idx >= 0; ) + { + struct Osc* osc = &this->oscs [idx]; + int vol = 0; + int amp = 0; + + // Determine what will be generated + struct Blip_Buffer* const out = osc->output; + if ( out ) + { + // volumes [i] ~= 64 * pow( 1.26, 15 - i ) / pow( 1.26, 15 ) + static unsigned char const volumes [16] ICONST_ATTR = { + 64, 50, 40, 32, 25, 20, 16, 13, 10, 8, 6, 5, 4, 3, 2, 0 + }; + + vol = volumes [osc->volume]; + amp = (osc->phase & 1) * vol; + + // Square freq above 16 kHz yields constant amplitude at half volume + if ( idx != noise_osc && osc->period < this->min_tone_period ) + { + amp = vol >> 1; + vol = 0; + } + + // Update amplitude + int delta = amp - osc->last_amp; + if ( delta ) + { + osc->last_amp = amp; + /* norm_synth.offset( last_time, delta, out ); */ + Synth_offset( &this->synth, this->last_time, delta, out ); + /* out->set_modified(); */ + Blip_set_modified( out ); + } + } + + // Generate wave + blip_time_t time = this->last_time + osc->delay; + if ( time < end_time ) + { + // Calculate actual period + int period = osc->period; + if ( idx == noise_osc ) + { + period = 0x20 << (period & 3); + if ( period == 0x100 ) + period = this->oscs [2].period * 2; + } + period *= 0x10; + if ( !period ) + period = 0x10; + + // Maintain phase when silent + int phase = osc->phase; + if ( !vol ) + { + int count = (end_time - time + period - 1) / period; + time += count * period; + if ( idx != noise_osc ) // TODO: maintain noise LFSR phase? + phase ^= count & 1; + } + else + { + int delta = amp * 2 - vol; + + if ( idx != noise_osc ) + { + // Square + do + { + delta = -delta; + /* norm_synth.offset( time, delta, out ); */ + Synth_offset( &this->synth, time, delta, out ); + time += period; + } + while ( time < end_time ); + phase = (delta >= 0); + } + else + { + // Noise + unsigned const feedback = (osc->period & 4 ? this->noise_feedback : this->looped_feedback); + do + { + unsigned changed = phase + 1; + phase = ((phase & 1) * feedback) ^ (phase >> 1); + if ( changed & 2 ) // true if bits 0 and 1 differ + { + delta = -delta; + /* fast_synth.offset_inline( time, delta, out ); */ + Synth_offset_inline( &this->synth, time, delta, out ); + } + time += period; + } + while ( time < end_time ); + check( phase ); + } + osc->last_amp = (phase & 1) * vol; + Blip_set_modified( out ); + } + osc->phase = phase; + } + osc->delay = time - end_time; + } + this->last_time = end_time; +} + +void Sms_apu_write_ggstereo( struct Sms_Apu* this, blip_time_t time, int data ) +{ + require( (unsigned) data <= 0xFF ); + + run_until( this, time ); + this->ggstereo = data; + + int i; + for ( i = sms_osc_count; --i >= 0; ) + { + struct Osc* osc = &this->oscs [i]; + + struct Blip_Buffer* old = osc->output; + osc->output = osc->outputs [calc_output( this, i )]; + if ( osc->output != old ) + { + int delta = -osc->last_amp; + if ( delta ) + { + osc->last_amp = 0; + if ( old ) + { + Blip_set_modified( old ); + Synth_offset( &this->synth, this->last_time, delta, old ); + } + } + } + } +} + +void Sms_apu_write_data( struct Sms_Apu* this, blip_time_t time, int data ) +{ + require( (unsigned) data <= 0xFF ); + + run_until( this, time ); + + if ( data & 0x80 ) + this->latch = data; + + // We want the raw values written so our save state format can be + // as close to hardware as possible and unspecific to any emulator. + int idx = this->latch >> 5 & 3; + struct Osc* osc = &this->oscs [idx]; + if ( this->latch & 0x10 ) + { + osc->volume = data & 0x0F; + } + else + { + if ( idx == noise_osc ) + osc->phase = 0x8000; // reset noise LFSR + + // Replace high 6 bits/low 4 bits of register with data + int lo = osc->period; + int hi = data << 4; + if ( idx == noise_osc || (data & 0x80) ) + { + hi = lo; + lo = data; + } + osc->period = (hi & 0x3F0) | (lo & 0x00F); + } +} + +void Sms_apu_end_frame( struct Sms_Apu* this, blip_time_t end_time ) +{ + if ( end_time > this->last_time ) + run_until( this, end_time ); + + this->last_time -= end_time; + assert( this->last_time >= 0 ); +} diff --git a/apps/codecs/libgme/sms_apu.h b/apps/codecs/libgme/sms_apu.h new file mode 100644 index 0000000000..25f4e74ae5 --- /dev/null +++ b/apps/codecs/libgme/sms_apu.h @@ -0,0 +1,63 @@ +// Sega Master System SN76489 PSG sound chip emulator + +// Sms_Snd_Emu 0.1.2 +#ifndef SMS_APU_H +#define SMS_APU_H + +#include "blargg_common.h" +#include "blip_buffer.h" + +// 0: Square 1, 1: Square 2, 2: Square 3, 3: Noise +enum { sms_osc_count = 4 }; // 0 <= chan < osc_count + +struct Osc +{ + struct Blip_Buffer* outputs [4]; // NULL, right, left, center + struct Blip_Buffer* output; + int last_amp; + + int volume; + int period; + int delay; + unsigned phase; +}; + +struct Sms_Apu { + struct Osc oscs [sms_osc_count]; + int ggstereo; + int latch; + + blip_time_t last_time; + int min_tone_period; + unsigned noise_feedback; + unsigned looped_feedback; + struct Blip_Synth synth; +}; + +// Basics + +void Sms_apu_init( struct Sms_Apu* this ); + +// Sets buffer(s) to generate sound into, or 0 to mute. If only center is not 0, +// output is mono. +void Sms_apu_set_output( struct Sms_Apu* this, int i, struct Blip_Buffer* center, struct Blip_Buffer* left, struct Blip_Buffer* right); + +// Emulates to time t, then writes data to Game Gear left/right assignment byte +void Sms_apu_write_ggstereo( struct Sms_Apu* this, blip_time_t t, int data ) ICODE_ATTR; + +// Emulates to time t, then writes data +void Sms_apu_write_data( struct Sms_Apu* this, blip_time_t t, int data ) ICODE_ATTR; + +// Emulates to time t, then subtracts t from the current time. +// OK if previous write call had time slightly after t. +void Sms_apu_end_frame( struct Sms_Apu* this, blip_time_t t ) ICODE_ATTR; + +// More features + +// Resets sound chip and sets noise feedback bits and width +void Sms_apu_reset( struct Sms_Apu* this, unsigned noise_feedback, int noise_width ); + +// Sets overall volume, where 1.0 is normal +void Sms_apu_volume( struct Sms_Apu* this, double vol ); + +#endif diff --git a/apps/codecs/libgme/sms_fm_apu.c b/apps/codecs/libgme/sms_fm_apu.c new file mode 100644 index 0000000000..b15fc56680 --- /dev/null +++ b/apps/codecs/libgme/sms_fm_apu.c @@ -0,0 +1,82 @@ +#include "sms_fm_apu.h" + +#include "blargg_source.h" + +void Fm_apu_create( struct Sms_Fm_Apu* this ) +{ + Synth_init( &this->synth ); + Ym2413_init( &this->apu ); +} + +blargg_err_t Fm_apu_init( struct Sms_Fm_Apu* this, double clock_rate, double sample_rate ) +{ + this->period_ = (blip_time_t) (clock_rate / sample_rate + 0.5); + CHECK_ALLOC( !Ym2413_set_rate( &this->apu, sample_rate, clock_rate ) ); + + Fm_apu_set_output( this, 0 ); + Fm_apu_volume( this, 1.0 ); + Fm_apu_reset( this ); + return 0; +} + +void Fm_apu_reset( struct Sms_Fm_Apu* this ) +{ + this->addr = 0; + this->next_time = 0; + this->last_amp = 0; + + Ym2413_reset( &this->apu ); +} + +void fm_run_until( struct Sms_Fm_Apu* this, blip_time_t end_time ) ICODE_ATTR; +void Fm_apu_write_data( struct Sms_Fm_Apu* this, blip_time_t time, int data ) +{ + if ( time > this->next_time ) + fm_run_until( this, time ); + + Ym2413_write( &this->apu, this->addr, data ); +} + +void fm_run_until( struct Sms_Fm_Apu* this, blip_time_t end_time ) +{ + assert( end_time > this->next_time ); + + struct Blip_Buffer* const output = this->output_; + if ( !output ) + { + this->next_time = end_time; + return; + } + + blip_time_t time = this->next_time; + struct Ym2413_Emu* emu = &this->apu; + do + { + short samples [2]; + Ym2413_run( emu, 1, samples ); + int amp = (samples [0] + samples [1]) >> 1; + + int delta = amp - this->last_amp; + if ( delta ) + { + this->last_amp = amp; + Synth_offset_inline( &this->synth, time, delta, output ); + } + time += this->period_; + } + while ( time < end_time ); + + this->next_time = time; +} + +void Fm_apu_end_frame( struct Sms_Fm_Apu* this, blip_time_t time ) +{ + if ( time > this->next_time ) + fm_run_until( this, time ); + + this->next_time -= time; + assert( this->next_time >= 0 ); + + if ( this->output_ ) + Blip_set_modified( this->output_ ); +} diff --git a/apps/codecs/libgme/sms_fm_apu.h b/apps/codecs/libgme/sms_fm_apu.h new file mode 100644 index 0000000000..95e1f95e62 --- /dev/null +++ b/apps/codecs/libgme/sms_fm_apu.h @@ -0,0 +1,43 @@ +#ifndef SMS_FM_APU_H +#define SMS_FM_APU_H + +#include "blargg_common.h" +#include "blip_buffer.h" +#include "ym2413_emu.h" + +enum { fm_apu_osc_count = 1 }; + +struct Sms_Fm_Apu { + struct Blip_Buffer* output_; + blip_time_t next_time; + int last_amp; + int addr; + + int clock_; + int rate_; + blip_time_t period_; + + struct Blip_Synth synth; + struct Ym2413_Emu apu; +}; + +void Fm_apu_create( struct Sms_Fm_Apu* this ); + +static inline bool Fm_apu_supported( void ) { return Ym2413_supported(); } +blargg_err_t Fm_apu_init( struct Sms_Fm_Apu* this, double clock_rate, double sample_rate ); + +static inline void Fm_apu_set_output( struct Sms_Fm_Apu* this, struct Blip_Buffer* b ) +{ + this->output_ = b; +} + +static inline void Fm_apu_volume( struct Sms_Fm_Apu* this, double v ) { Synth_volume( &this->synth, 0.4 / 4096 * v ); } + +void Fm_apu_reset( struct Sms_Fm_Apu* this ); + +static inline void Fm_apu_write_addr( struct Sms_Fm_Apu* this, int data ) { this->addr = data; } +void Fm_apu_write_data( struct Sms_Fm_Apu* this, blip_time_t, int data ) ICODE_ATTR; + +void Fm_apu_end_frame( struct Sms_Fm_Apu* this, blip_time_t t ) ICODE_ATTR; + +#endif diff --git a/apps/codecs/libgme/vgm_emu.c b/apps/codecs/libgme/vgm_emu.c new file mode 100644 index 0000000000..7fed4ef6d1 --- /dev/null +++ b/apps/codecs/libgme/vgm_emu.c @@ -0,0 +1,1053 @@ +// Game_Music_Emu 0.5.5. http://www.slack.net/~ant/ + +#include "vgm_emu.h" + +#include "blargg_endian.h" +#include +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +const char* const gme_wrong_file_type = "Wrong file type for this emulator"; + +double const fm_gain = 3.0; // FM emulators are internally quieter to avoid 16-bit overflow +double const rolloff = 0.990; +double const oversample_factor = 1.5; + +int const silence_max = 6; // seconds +int const silence_threshold = 0x10; +long const fade_block_size = 512; +int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) + +// VGM commands (Spec v1.50) +enum { + cmd_gg_stereo = 0x4F, + cmd_psg = 0x50, + cmd_ym2413 = 0x51, + cmd_ym2612_port0 = 0x52, + cmd_ym2612_port1 = 0x53, + cmd_ym2151 = 0x54, + cmd_delay = 0x61, + cmd_delay_735 = 0x62, + cmd_delay_882 = 0x63, + cmd_byte_delay = 0x64, + cmd_end = 0x66, + cmd_data_block = 0x67, + cmd_short_delay = 0x70, + cmd_pcm_delay = 0x80, + cmd_pcm_seek = 0xE0, + + pcm_block_type = 0x00, + ym2612_dac_port = 0x2A, + ym2612_dac_pan_port = 0xB6 +}; + +void clear_track_vars( struct Vgm_Emu* this ) +{ + this->out_time = 0; + this->emu_time = 0; + this->emu_track_ended_ = true; + this->track_ended = true; + this->fade_start = INT_MAX / 2 + 1; + this->fade_step = 1; + this->silence_time = 0; + this->silence_count = 0; + this->buf_remain = 0; +} + +int play_frame( struct Vgm_Emu* this, blip_time_t blip_time, int sample_count, sample_t* buf ); +static int play_frame_( void* data, blip_time_t blip_time, int sample_count, short int* buf ) +{ + return play_frame( (struct Vgm_Emu*) data, blip_time, sample_count, buf ); +} + +void Vgm_init( struct Vgm_Emu* this ) +{ + this->sample_rate = 0; + this->mute_mask_ = 0; + this->tempo = 1.0; + + // defaults + this->max_initial_silence = 2; + this->silence_lookahead = 1; // tracks should already be trimmed + this->ignore_silence = false; + + // Disable oversampling by default + this->disable_oversampling = true; + this->psg_rate = 0; + + Sms_apu_init( &this->psg ); + Synth_init( &this->pcm ); + + Buffer_init( &this->buf ); + Buffer_init( &this->stereo_buf ); + this->blip_buf = &this->stereo_buf.bufs [0]; + + // Init fm chips + Ym2413_init( &this->ym2413 ); + Ym2612_init( &this->ym2612 ); + + // Init resampler + Resampler_init( &this->resampler ); + Resampler_set_callback( &this->resampler, play_frame_, this ); + + // Set sound gain, a value too high + // will cause saturation + Sound_set_gain(this, 1.0); + + // Unload + this->voice_count = 0; + clear_track_vars( this ); +} + +// Track info + +static byte const* skip_gd3_str( byte const* in, byte const* end ) +{ + while ( end - in >= 2 ) + { + in += 2; + if ( !(in [-2] | in [-1]) ) + break; + } + return in; +} + +static byte const* get_gd3_str( byte const* in, byte const* end, char* field ) +{ + byte const* mid = skip_gd3_str( in, end ); + int i, len = (mid - in) / 2 - 1; + if ( field && len > 0 ) + { + len = min( len, (int) gme_max_field ); + field [len] = 0; + for ( i = 0; i < len; i++ ) + field [i] = (in [i * 2 + 1] ? '?' : in [i * 2]); // TODO: convert to utf-8 + } + return mid; +} + +static byte const* get_gd3_pair( byte const* in, byte const* end, char* field ) +{ + return skip_gd3_str( get_gd3_str( in, end, field ), end ); +} + +static void parse_gd3( byte const* in, byte const* end, struct track_info_t* out ) +{ + in = get_gd3_pair( in, end, out->song ); + in = get_gd3_pair( in, end, out->game ); + in = get_gd3_pair( in, end, NULL ); // Skip system + in = get_gd3_pair( in, end, out->author ); +} + +int const gd3_header_size = 12; + +static long check_gd3_header( byte const* h, long remain ) +{ + if ( remain < gd3_header_size ) return 0; + if ( memcmp( h, "Gd3 ", 4 ) ) return 0; + if ( get_le32( h + 4 ) >= 0x200 ) return 0; + + long gd3_size = get_le32( h + 8 ); + if ( gd3_size > remain - gd3_header_size ) + gd3_size = remain - gd3_header_size; + return gd3_size; +} + +byte const* gd3_data( struct Vgm_Emu* this, int* size ) +{ + if ( size ) + *size = 0; + + long gd3_offset = get_le32( header( this )->gd3_offset ) - 0x2C; + if ( gd3_offset < 0 ) + return 0; + + byte const* gd3 = this->file_begin + header_size + gd3_offset; + long gd3_size = check_gd3_header( gd3, this->file_end - gd3 ); + if ( !gd3_size ) + return 0; + + if ( size ) + *size = gd3_size + gd3_header_size; + + return gd3; +} + +static void get_vgm_length( struct header_t const* h, struct track_info_t* out ) +{ + long length = get_le32( h->track_duration ) * 10 / 441; + if ( length > 0 ) + { + long loop = get_le32( h->loop_duration ); + if ( loop > 0 && get_le32( h->loop_offset ) ) + { + out->loop_length = loop * 10 / 441; + out->intro_length = length - out->loop_length; + } + else + { + out->length = length; // 1000 / 44100 (VGM files used 44100 as timebase) + out->intro_length = length; // make it clear that track is no longer than length + out->loop_length = 0; + } + } +} + +blargg_err_t track_info( struct Vgm_Emu* this, struct track_info_t* out ) +{ + memset(out, 0, sizeof out); + get_vgm_length( header( this ), out ); + + int size; + byte const* gd3 = gd3_data( this, &size ); + if ( gd3 ) + parse_gd3( gd3 + gd3_header_size, gd3 + size, out ); + + return 0; +} + +static blargg_err_t check_vgm_header( struct header_t* h ) +{ + if ( memcmp( h->tag, "Vgm ", 4 ) ) + return gme_wrong_file_type; + return 0; +} + +void set_voice( struct Vgm_Emu* this, int i, struct Blip_Buffer* c, struct Blip_Buffer* l, struct Blip_Buffer* r ) +{ + if ( i < sms_osc_count ) { + int j; + for ( j = sms_osc_count; --j >= 0; ) + Sms_apu_set_output( &this->psg, j, c, l, r ); + } +} + +blargg_err_t setup_fm( struct Vgm_Emu* this ); +blargg_err_t Vgm_load_mem( struct Vgm_Emu* this, byte const* new_data, long new_size, bool parse_info ) +{ + // Unload + this->voice_count = 0; + clear_track_vars( this ); + + // Clear info + memset( &this->info, 0, sizeof this->info ); + + assert( offsetof (struct header_t,unused2 [8]) == header_size ); + + if ( new_size <= header_size ) + return gme_wrong_file_type; + + // Reset data pointers + this->file_begin = new_data; + this->file_end = new_data + new_size; + + struct header_t* h = (struct header_t*) new_data; + RETURN_ERR( check_vgm_header( h ) ); + check( get_le32( h.version ) <= 0x150 ); + + // If this was VGZ file gd3 parse info + if ( parse_info ) { + track_info( this, &this->info ); + + // If file was trimmed add an + // incomplete token to the game tag + if ( get_le32( h->data_size ) > (unsigned) new_size ) { + *((char *) this->file_end) = cmd_end; + strcat(this->info.game, "(Trimmed VGZ file)" ); + } + } + + // Get loop + this->loop_begin = this->file_end; + + // If file was trimmed don't loop + if ( get_le32( h->loop_offset ) && get_le32( h->data_size ) <= (unsigned) new_size ) + this->loop_begin = &new_data [get_le32( h->loop_offset ) + offsetof (struct header_t,loop_offset)]; + + // PSG rate + this->psg_rate = get_le32( h->psg_rate ); + if ( !this->psg_rate ) + this->psg_rate = 3579545; + + Buffer_clock_rate( &this->stereo_buf, this->psg_rate ); + + // Disable FM + this->fm_rate = 0; + Ym2612_enable( &this->ym2612, false ); + Ym2413_enable( &this->ym2413, false ); + + Sound_set_tempo( this, 1 ); + + this->voice_count = sms_osc_count; + + RETURN_ERR( setup_fm( this ) ); + + // do after FM in case output buffer is changed + // setup buffer + this->clock_rate_ = this->psg_rate; + Buffer_clock_rate( &this->buf, this->psg_rate ); + + // Setup bass + this->buf_changed_count = Buffer_channels_changed_count( &this->buf ); + + // Post load + Sound_set_tempo( this, this->tempo ); + Sound_mute_voices( this, this->mute_mask_ ); + + return 0; +} + +void update_fm_rates( struct Vgm_Emu* this, int* ym2413_rate, int* ym2612_rate ); +blargg_err_t init_fm( struct Vgm_Emu* this, double* rate ) +{ + int ym2612_rate = get_le32( header( this )->ym2612_rate ); + int ym2413_rate = get_le32( header( this )->ym2413_rate ); + if ( ym2413_rate && get_le32( header( this )->version ) < 0x110 ) + update_fm_rates( this, &ym2413_rate, &ym2612_rate ); + + if ( ym2612_rate ) + { + if ( !*rate ) + *rate = ym2612_rate / 144.0; + RETURN_ERR( Ym2612_set_rate( &this->ym2612, *rate, ym2612_rate ) ); + Ym2612_enable( &this->ym2612, true ); + } + else if ( ym2413_rate ) + { + if ( !*rate ) + *rate = ym2413_rate / 72.0; + int result = Ym2413_set_rate( &this->ym2413, *rate, ym2413_rate ); + if ( result == 2 ) + return "YM2413 FM sound not supported"; + CHECK_ALLOC( !result ); + Ym2413_enable( &this->ym2413, true ); + } + + this->fm_rate = *rate; + + return 0; +} + +blargg_err_t setup_fm( struct Vgm_Emu* this ) +{ + double fm_rate = 0.0; + if ( !this->disable_oversampling ) + this->fm_rate = this->sample_rate * oversample_factor; + RETURN_ERR( init_fm( this, &fm_rate ) ); + + if ( uses_fm( this ) ) + { + this->voice_count = 8; + RETURN_ERR( Resampler_setup( &this->resampler, fm_rate / this->sample_rate, rolloff, fm_gain * this->gain ) ); + RETURN_ERR( Resampler_reset( &this->resampler, Buffer_length( &this->stereo_buf ) * this->sample_rate / 1000 ) ); + Sms_apu_volume( &this->psg, 0.195 * fm_gain * this->gain ); + } + else + { + Sms_apu_volume( &this->psg, this->gain ); + } + + return 0; +} + +// Emulation + +blip_time_t run( struct Vgm_Emu* this, vgm_time_t end_time ); +blargg_err_t run_clocks( struct Vgm_Emu* this, blip_time_t* time_io, int msec ) +{ + *time_io = run( this, msec * this->vgm_rate / 1000 ); + Sms_apu_end_frame( &this->psg, *time_io ); + return 0; +} + + + +blargg_err_t play_( struct Vgm_Emu* this, long count, sample_t* out ) +{ + if ( !uses_fm( this ) ) { + long remain = count; + while ( remain ) + { + remain -= Buffer_read_samples( &this->buf, &out [count - remain], remain ); + if ( remain ) + { + if ( this->buf_changed_count != Buffer_channels_changed_count( &this->buf ) ) + { + this->buf_changed_count = Buffer_channels_changed_count( &this->buf ); + + // Remute voices + Sound_mute_voices( this, this->mute_mask_ ); + } + int msec = Buffer_length( &this->buf ); + blip_time_t clocks_emulated = (blargg_long) msec * this->clock_rate_ / 1000 - 100; + RETURN_ERR( run_clocks( this, &clocks_emulated, msec ) ); + assert( clocks_emulated ); + Buffer_end_frame( &this->buf, clocks_emulated ); + } + } + + return 0; + } + + Resampler_play( &this->resampler, count, out, &this->stereo_buf ); + return 0; +} + +// Vgm_Emu_impl + +inline int command_len( int command ) +{ + static byte const lens [0x10] ICONST_ATTR = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 1,1,1,2,2,3,1,1,1,1,3,3,4,4,5,5 + }; + int len = lens [command >> 4]; + check( len != 1 ); + return len; +} + +inline fm_time_t to_fm_time( struct Vgm_Emu* this, vgm_time_t t ) +{ + return (t * this->fm_time_factor + this->fm_time_offset) >> fm_time_bits; +} + +inline blip_time_t to_psg_time( struct Vgm_Emu* this, vgm_time_t t ) +{ + return (t * this->blip_time_factor) >> blip_time_bits; +} + +void write_pcm( struct Vgm_Emu* this, vgm_time_t vgm_time, int amp ) +{ + if ( this->blip_buf ) + { + check( amp >= 0 ); + blip_time_t blip_time = to_psg_time( this, vgm_time ); + int old = this->dac_amp; + int delta = amp - old; + this->dac_amp = amp; + Blip_set_modified( this->blip_buf ); + if ( old >= 0 ) // first write is ignored, to avoid click + Synth_offset_inline( &this->pcm, blip_time, delta, this->blip_buf ); + else + this->dac_amp |= this->dac_disabled; + } +} + +blip_time_t run( struct Vgm_Emu* this, vgm_time_t end_time ) +{ + vgm_time_t vgm_time = this->vgm_time; + byte const* pos = this->pos; + if ( pos >= this->file_end ) + { + this->emu_track_ended_ = true; + /* if ( pos > data_end ) + warning( "Stream lacked end event" ); */ + } + + while ( vgm_time < end_time && pos < this->file_end ) + { + // TODO: be sure there are enough bytes left in stream for particular command + // so we don't read past end + switch ( *pos++ ) + { + case cmd_end: + pos = this->loop_begin; // if not looped, loop_begin == data_end + break; + + case cmd_delay_735: + vgm_time += 735; + break; + + case cmd_delay_882: + vgm_time += 882; + break; + + case cmd_gg_stereo: + Sms_apu_write_ggstereo( &this->psg, to_psg_time( this, vgm_time ), *pos++ ); + break; + + case cmd_psg: + Sms_apu_write_data( &this->psg, to_psg_time( this, vgm_time ), *pos++ ); + break; + + case cmd_delay: + vgm_time += pos [1] * 0x100 + pos [0]; + pos += 2; + break; + + case cmd_byte_delay: + vgm_time += *pos++; + break; + + case cmd_ym2413: + if ( Ym2413_run_until( &this->ym2413, to_fm_time( this, vgm_time ) ) ) + Ym2413_write( &this->ym2413, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2612_port0: + if ( pos [0] == ym2612_dac_port ) + { + write_pcm( this, vgm_time, pos [1] ); + } + else if ( Ym2612_run_until( &this->ym2612, to_fm_time( this, vgm_time ) ) ) + { + if ( pos [0] == 0x2B ) + { + this->dac_disabled = (pos [1] >> 7 & 1) - 1; + this->dac_amp |= this->dac_disabled; + } + Ym2612_write0( &this->ym2612, pos [0], pos [1] ); + } + pos += 2; + break; + + case cmd_ym2612_port1: + if ( Ym2612_run_until( &this->ym2612, to_fm_time( this, vgm_time ) ) ) + { + if ( pos [0] == ym2612_dac_pan_port ) + { + struct Blip_Buffer* blip_buf = NULL; + switch ( pos [1] >> 6 ) + { + case 0: blip_buf = NULL; break; + case 1: blip_buf = &this->stereo_buf.bufs [2]; break; + case 2: blip_buf = &this->stereo_buf.bufs [1]; break; + case 3: blip_buf = &this->stereo_buf.bufs [0]; break; + } + this->blip_buf = blip_buf; + } + + Ym2612_write1( &this->ym2612, pos [0], pos [1] ); + } + pos += 2; + break; + + case cmd_data_block: { + check( *pos == cmd_end ); + int type = pos [1]; + long size = get_le32( pos + 2 ); + pos += 6; + if ( type == pcm_block_type ) + this->pcm_data = pos; + pos += size; + break; + } + + case cmd_pcm_seek: + this->pcm_pos = this->pcm_data + pos [3] * 0x1000000 + pos [2] * 0x10000 + + pos [1] * 0x100 + pos [0]; + pos += 4; + break; + + default: { + int cmd = pos [-1]; + switch ( cmd & 0xF0 ) + { + case cmd_pcm_delay: + write_pcm( this, vgm_time, *this->pcm_pos++ ); + vgm_time += cmd & 0x0F; + break; + + case cmd_short_delay: + vgm_time += (cmd & 0x0F) + 1; + break; + + case 0x50: + pos += 2; + break; + + default: + pos += command_len( cmd ) - 1; + /* warning( "Unknown stream event" ); */ + } + } + } + } + vgm_time -= end_time; + this->pos = pos; + this->vgm_time = vgm_time; + + return to_psg_time( this, end_time ); +} + +int play_frame( struct Vgm_Emu* this, blip_time_t blip_time, int sample_count, blip_sample_t out [] ) +{ + // to do: timing is working mostly by luck + int min_pairs = (unsigned) sample_count / 2; + int vgm_time = (min_pairs << fm_time_bits) / this->fm_time_factor - 1; + assert( to_fm_time( this, vgm_time ) <= min_pairs ); + int pairs; + while ( (pairs = to_fm_time( this, vgm_time )) < min_pairs ) + vgm_time++; + //debug_printf( "pairs: %d, min_pairs: %d\n", pairs, min_pairs ); + + if ( Ym2612_enabled( &this->ym2612 ) ) + { + Ym2612_begin_frame( &this->ym2612, out ); + memset( out, 0, pairs * stereo * sizeof *out ); + } + else if ( Ym2413_enabled( &this->ym2413 ) ) + { + Ym2413_begin_frame( &this->ym2413, out ); + } + + run( this, vgm_time ); + Ym2612_run_until( &this->ym2612, pairs ); + Ym2413_run_until( &this->ym2413, pairs ); + + this->fm_time_offset = (vgm_time * this->fm_time_factor + this->fm_time_offset) - (pairs << fm_time_bits); + + Sms_apu_end_frame( &this->psg, blip_time ); + + return pairs * stereo; +} + +// Update pre-1.10 header FM rates by scanning commands +void update_fm_rates( struct Vgm_Emu* this, int* ym2413_rate, int* ym2612_rate ) +{ + byte const* p = this->file_begin + 0x40; + while ( p < this->file_end ) + { + switch ( *p ) + { + case cmd_end: + return; + + case cmd_psg: + case cmd_byte_delay: + p += 2; + break; + + case cmd_delay: + p += 3; + break; + + case cmd_data_block: + p += 7 + get_le32( p + 3 ); + break; + + case cmd_ym2413: + *ym2612_rate = 0; + return; + + case cmd_ym2612_port0: + case cmd_ym2612_port1: + *ym2612_rate = *ym2413_rate; + *ym2413_rate = 0; + return; + + case cmd_ym2151: + *ym2413_rate = 0; + *ym2612_rate = 0; + return; + + default: + p += command_len( *p ); + } + } +} + + +// Music Emu + +blargg_err_t Vgm_set_sample_rate( struct Vgm_Emu* this, long rate ) +{ + require( !this->sample_rate ); // sample rate can't be changed once set + RETURN_ERR( Buffer_set_sample_rate( &this->stereo_buf, rate, 1000 / 30 ) ); + RETURN_ERR( Buffer_set_sample_rate( &this->buf, rate, 1000 / 20 ) ); + + // Set bass frequency + Buffer_bass_freq( &this->buf, 80 ); + + this->sample_rate = rate; + return 0; +} + +void Sound_mute_voice( struct Vgm_Emu* this, int index, bool mute ) +{ + require( (unsigned) index < (unsigned) this->voice_count ); + int bit = 1 << index; + int mask = this->mute_mask_ | bit; + if ( !mute ) + mask ^= bit; + Sound_mute_voices( this, mask ); +} + +void Sound_mute_voices( struct Vgm_Emu* this, int mask ) +{ + require( this->sample_rate ); // sample rate must be set first + this->mute_mask_ = mask; + + int i; + for ( i = this->voice_count; i--; ) + { + if ( mask & (1 << i) ) + { + set_voice( this, i, 0, 0, 0 ); + } + else + { + struct channel_t ch = Buffer_channel( &this->buf ); + assert( (ch.center && ch.left && ch.right) || + (!ch.center && !ch.left && !ch.right) ); // all or nothing + set_voice( this, i, ch.center, ch.left, ch.right ); + } + } + + // TODO: what was this for? + //core.pcm.output( &core.blip_buf ); + + // TODO: silence PCM if FM isn't used? + if ( uses_fm( this ) ) + { + for ( i = sms_osc_count; --i >= 0; ) + Sms_apu_set_output( &this->psg, i, ( mask & 0x80 ) ? 0 : &this->stereo_buf.bufs [0], NULL, NULL ); + if ( Ym2612_enabled( &this->ym2612 ) ) + { + Synth_volume( &this->pcm, (mask & 0x40) ? 0.0 : 0.1115 / 256 * fm_gain * this->gain ); + Ym2612_mute_voices( &this->ym2612, mask ); + } + + if ( Ym2413_enabled( &this->ym2413 ) ) + { + int m = mask & 0x3F; + if ( mask & 0x20 ) + m |= 0x01E0; // channels 5-8 + if ( mask & 0x40 ) + m |= 0x3E00; + Ym2413_mute_voices( &this->ym2413, m ); + } + } +} + +void Sound_set_tempo( struct Vgm_Emu* this, double t ) +{ + require( this->sample_rate ); // sample rate must be set first + double const min = 0.02; + double const max = 4.00; + if ( t < min ) t = min; + if ( t > max ) t = max; + this->tempo = t; + + if ( this->file_begin ) + { + this->vgm_rate = (long) (44100 * t + 0.5); + this->blip_time_factor = (int) ((double) + (1 << blip_time_bits) / this->vgm_rate * Blip_clock_rate( &this->stereo_buf.bufs [0] ) + 0.5); + //debug_printf( "blip_time_factor: %ld\n", blip_time_factor ); + //debug_printf( "vgm_rate: %ld\n", vgm_rate ); + // TODO: remove? calculates vgm_rate more accurately (above differs at most by one Hz only) + //blip_time_factor = (long) floor( double (1L << blip_time_bits) * psg_rate / 44100 / t + 0.5 ); + //vgm_rate = (long) floor( double (1L << blip_time_bits) * psg_rate / blip_time_factor + 0.5 ); + + this->fm_time_factor = 2 + (int) (this->fm_rate * (1 << fm_time_bits) / this->vgm_rate + 0.5); + } +} + +void fill_buf( struct Vgm_Emu *this ); +blargg_err_t Vgm_start_track( struct Vgm_Emu* this ) +{ + clear_track_vars( this ); + + Sms_apu_reset( &this->psg, get_le16( header( this )->noise_feedback ), header( this )->noise_width ); + + this->blip_buf = &this->stereo_buf.bufs [0]; + + this->dac_disabled = -1; + this->pos = this->file_begin + header_size; + this->pcm_data = this->pos; + this->pcm_pos = this->pos; + this->dac_amp = -1; + this->vgm_time = 0; + if ( get_le32( header( this )->version ) >= 0x150 ) + { + long data_offset = get_le32( header( this )->data_offset ); + check( data_offset ); + if ( data_offset ) + this->pos += data_offset + offsetof (struct header_t,data_offset) - 0x40; + } + + if ( uses_fm( this ) ) + { + if ( Ym2413_enabled( &this->ym2413 ) ) + Ym2413_reset( &this->ym2413 ); + + if ( Ym2612_enabled( &this->ym2612 ) ) + Ym2612_reset( &this->ym2612 ); + + Buffer_clear( &this->stereo_buf ); + Resampler_clear( &this->resampler ); + } + + this->fm_time_offset = 0; + + Buffer_clear( &this->buf ); + + this->emu_track_ended_ = false; + this->track_ended = false; + + if ( !this->ignore_silence ) + { + // play until non-silence or end of track + long end; + for ( end = this->max_initial_silence * stereo * this->sample_rate; this->emu_time < end; ) + { + fill_buf( this ); + if ( this->buf_remain | (int) this->emu_track_ended_ ) + break; + } + + this->emu_time = this->buf_remain; + this->out_time = 0; + this->silence_time = 0; + this->silence_count = 0; + } + /* return track_ended() ? warning() : 0; */ + return 0; +} + +// Tell/Seek + +blargg_long msec_to_samples( blargg_long msec, long sample_rate ) +{ + blargg_long sec = msec / 1000; + msec -= sec * 1000; + return (sec * sample_rate + msec * sample_rate / 1000) * stereo; +} + +long Track_tell( struct Vgm_Emu* this ) +{ + blargg_long rate = this->sample_rate * stereo; + blargg_long sec = this->out_time / rate; + return sec * 1000 + (this->out_time - sec * rate) * 1000 / rate; +} + +blargg_err_t Track_seek( struct Vgm_Emu* this, long msec ) +{ + blargg_long time = msec_to_samples( msec, this->sample_rate ); + if ( time < this->out_time ) + RETURN_ERR( Vgm_start_track( this ) ); + return Track_skip( this, time - this->out_time ); +} + +blargg_err_t skip_( struct Vgm_Emu* this, long count ); +blargg_err_t Track_skip( struct Vgm_Emu* this, long count ) +{ + this->out_time += count; + + // remove from silence and buf first + { + long n = min( count, this->silence_count ); + this->silence_count -= n; + count -= n; + + n = min( count, this->buf_remain ); + this->buf_remain -= n; + count -= n; + } + + if ( count && !this->emu_track_ended_ ) + { + this->emu_time += count; + if ( skip_( this, count ) ) + this->emu_track_ended_ = true; + } + + if ( !(this->silence_count | this->buf_remain) ) // caught up to emulator, so update track ended + this->track_ended |= this->emu_track_ended_; + + return 0; +} + +blargg_err_t skip_( struct Vgm_Emu* this, long count ) +{ + // for long skip, mute sound + const long threshold = 30000; + if ( count > threshold ) + { + int saved_mute = this->mute_mask_; + Sound_mute_voices( this, ~0 ); + + while ( count > threshold / 2 && !this->emu_track_ended_ ) + { + RETURN_ERR( play_( this, buf_size, this->buf_ ) ); + count -= buf_size; + } + + Sound_mute_voices( this, saved_mute ); + } + + while ( count && !this->emu_track_ended_ ) + { + long n = buf_size; + if ( n > count ) + n = count; + count -= n; + RETURN_ERR( play_( this, n, this->buf_ ) ); + } + return 0; +} + +// Fading + +void Track_set_fade( struct Vgm_Emu* this, long start_msec, long length_msec ) +{ + this->fade_step = this->sample_rate * length_msec / (fade_block_size * fade_shift * 1000 / stereo); + this->fade_start = msec_to_samples( start_msec, this->sample_rate ); +} + +// unit / pow( 2.0, (double) x / step ) +static int int_log( blargg_long x, int step, int unit ) +{ + int shift = x / step; + int fraction = (x - shift * step) * unit / step; + return ((unit - fraction) + (fraction >> 1)) >> shift; +} + +void handle_fade( struct Vgm_Emu* this, long out_count, sample_t* out ) +{ + int i; + for ( i = 0; i < out_count; i += fade_block_size ) + { + int const shift = 14; + int const unit = 1 << shift; + int gain = int_log( (this->out_time + i - this->fade_start) / fade_block_size, + this->fade_step, unit ); + if ( gain < (unit >> fade_shift) ) + this->track_ended = this->emu_track_ended_ = true; + + sample_t* io = &out [i]; + int count; + for ( count = min( fade_block_size, out_count - i ); count; --count ) + { + *io = (sample_t) ((*io * gain) >> shift); + ++io; + } + } +} + +// Silence detection + +void emu_play( struct Vgm_Emu* this, long count, sample_t* out ) +{ + this->emu_time += count; + if ( !this->emu_track_ended_ ) { + if ( play_( this, count, out ) ) + this->emu_track_ended_ = true; + } + else + memset( out, 0, count * sizeof *out ); +} + +// number of consecutive silent samples at end +static long count_silence( sample_t* begin, long size ) +{ + sample_t first = *begin; + *begin = silence_threshold; // sentinel + sample_t* p = begin + size; + while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { } + *begin = first; + return size - (p - begin); +} + +// fill internal buffer and check it for silence +void fill_buf( struct Vgm_Emu* this ) +{ + assert( !this->buf_remain ); + if ( !this->emu_track_ended_ ) + { + emu_play( this, buf_size, this->buf_ ); + long silence = count_silence( this->buf_, buf_size ); + if ( silence < buf_size ) + { + this->silence_time = this->emu_time - silence; + this->buf_remain = buf_size; + return; + } + } + this->silence_count += buf_size; +} + +blargg_err_t Vgm_play( struct Vgm_Emu* this, long out_count, sample_t* out ) +{ + if ( this->track_ended ) + { + memset( out, 0, out_count * sizeof *out ); + } + else + { + require( out_count % stereo == 0 ); + + assert( this->emu_time >= this->out_time ); + + // prints nifty graph of how far ahead we are when searching for silence + //debug_printf( "%*s \n", int ((emu_time - out_time) * 7 / sample_rate()), "*" ); + + long pos = 0; + if ( this->silence_count ) + { + // during a run of silence, run emulator at >=2x speed so it gets ahead + long ahead_time = this->silence_lookahead * (this->out_time + out_count - this->silence_time) + this->silence_time; + while ( this->emu_time < ahead_time && !(this->buf_remain | this->emu_track_ended_) ) + fill_buf( this ); + + // fill with silence + pos = min( this->silence_count, out_count ); + memset( out, 0, pos * sizeof *out ); + this->silence_count -= pos; + + if ( this->emu_time - this->silence_time > silence_max * stereo * this->sample_rate ) + { + this->track_ended = this->emu_track_ended_ = true; + this->silence_count = 0; + this->buf_remain = 0; + } + } + + if ( this->buf_remain ) + { + // empty silence buf + long n = min( this->buf_remain, out_count - pos ); + memcpy( &out [pos], this->buf_ + (buf_size - this->buf_remain), n * sizeof *out ); + this->buf_remain -= n; + pos += n; + } + + // generate remaining samples normally + long remain = out_count - pos; + if ( remain ) + { + emu_play( this, remain, out + pos ); + this->track_ended |= this->emu_track_ended_; + + if ( !this->ignore_silence || this->out_time > this->fade_start ) + { + // check end for a new run of silence + long silence = count_silence( out + pos, remain ); + if ( silence < remain ) + this->silence_time = this->emu_time - silence; + + if ( this->emu_time - this->silence_time >= buf_size ) + fill_buf( this ); // cause silence detection on next play() + } + } + + if ( this->out_time > this->fade_start ) + handle_fade( this, out_count, out ); + } + this->out_time += out_count; + return 0; +} diff --git a/apps/codecs/libgme/vgm_emu.h b/apps/codecs/libgme/vgm_emu.h new file mode 100644 index 0000000000..deb64bc7e0 --- /dev/null +++ b/apps/codecs/libgme/vgm_emu.h @@ -0,0 +1,211 @@ +// Sega Master System/Mark III, Sega Genesis/Mega Drive, BBC Micro VGM music file emulator + +// Game_Music_Emu 0.5.5 +#ifndef VGM_EMU_H +#define VGM_EMU_H + +#include "blargg_common.h" +#include "blargg_source.h" +#include "resampler.h" +#include "multi_buffer.h" +#include "ym2413_emu.h" +#include "ym2612_emu.h" +#include "sms_apu.h" + +typedef short sample_t; +typedef int vgm_time_t; +typedef int fm_time_t; + +enum { fm_time_bits = 12 }; +enum { blip_time_bits = 12 }; +enum { buf_size = 2048 }; + +// VGM header format +enum { header_size = 0x40 }; +struct header_t +{ + char tag [4]; + byte data_size [4]; + byte version [4]; + byte psg_rate [4]; + byte ym2413_rate [4]; + byte gd3_offset [4]; + byte track_duration [4]; + byte loop_offset [4]; + byte loop_duration [4]; + byte frame_rate [4]; + byte noise_feedback [2]; + byte noise_width; + byte unused1; + byte ym2612_rate [4]; + byte ym2151_rate [4]; + byte data_offset [4]; + byte unused2 [8]; +}; + +enum { gme_max_field = 63 }; +struct track_info_t +{ + /* times in milliseconds; -1 if unknown */ + long length; + long intro_length; + long loop_length; + + /* empty string if not available */ + char game [64]; + char song [96]; + char author [64]; +}; + +// Emulates VGM music using SN76489/SN76496 PSG, YM2612, and YM2413 FM sound chips. +// Supports custom sound buffer and frequency equalization when VGM uses just the PSG. +// FM sound chips can be run at their proper rates, or slightly higher to reduce +// aliasing on high notes. Currently YM2413 support requires that you supply a +// YM2413 sound chip emulator. I can provide one I've modified to work with the library. +struct Vgm_Emu { + double fm_rate; + long psg_rate; + long vgm_rate; + bool disable_oversampling; + + long fm_time_offset; + int fm_time_factor; + + int blip_time_factor; + + byte const* file_begin; + byte const* file_end; + + vgm_time_t vgm_time; + byte const* loop_begin; + byte const* pos; + + byte const* pcm_data; + byte const* pcm_pos; + int dac_amp; + int dac_disabled; // -1 if disabled + + struct Blip_Buffer* blip_buf; + + // general + long clock_rate_; + unsigned buf_changed_count; + int max_initial_silence; + int voice_count; + int mute_mask_; + double tempo; + double gain; + + long sample_rate; + + // track-specific + blargg_long out_time; // number of samples played since start of track + blargg_long emu_time; // number of samples emulator has generated since start of track + bool emu_track_ended_; // emulator has reached end of track + volatile bool track_ended; + + // fading + blargg_long fade_start; + int fade_step; + + // silence detection + int silence_lookahead; // speed to run emulator when looking ahead for silence + bool ignore_silence; + long silence_time; // number of samples where most recent silence began + long silence_count; // number of samples of silence to play before using buf + long buf_remain; // number of samples left in silence buffer + + // larger items at the end + struct track_info_t info; + sample_t buf_ [buf_size]; + + struct Ym2612_Emu ym2612; + struct Ym2413_Emu ym2413; + + struct Sms_Apu psg; + struct Blip_Synth pcm; + struct Stereo_Buffer stereo_buf; + + struct Resampler resampler; + + struct Stereo_Buffer buf; +}; + +void Vgm_init( struct Vgm_Emu* this ); + +// Disable running FM chips at higher than normal rate. Will result in slightly +// more aliasing of high notes. +static inline void Vgm_disable_oversampling( struct Vgm_Emu* this, bool disable ) { this->disable_oversampling = disable; } + +// Header for currently loaded file +static inline struct header_t *header( struct Vgm_Emu* this ) { return (struct header_t*) this->file_begin; } + +// Basic functionality (see Gme_File.h for file loading/track info functions) +blargg_err_t Vgm_load_mem( struct Vgm_Emu* this, byte const* new_data, long new_size, bool parse_info ); + +// True if any FM chips are used by file. Always false until init_fm() +// is called. +static inline bool uses_fm( struct Vgm_Emu* this ) { return Ym2612_enabled( &this->ym2612 ) || Ym2413_enabled( &this->ym2413 ); } + +// Set output sample rate. Must be called only once before loading file. +blargg_err_t Vgm_set_sample_rate( struct Vgm_Emu* this, long sample_rate ); + +// Start a track, where 0 is the first track. Also clears warning string. +blargg_err_t Vgm_start_track( struct Vgm_Emu* this ); + +// Generate 'count' samples info 'buf'. Output is in stereo. Any emulation +// errors set warning string, and major errors also end track. +blargg_err_t Vgm_play( struct Vgm_Emu* this, long count, sample_t* buf ) ICODE_ATTR; + +// Track status/control + +// Number of milliseconds (1000 msec = 1 second) played since beginning of track +long Track_tell( struct Vgm_Emu* this ); + +// Seek to new time in track. Seeking backwards or far forward can take a while. +blargg_err_t Track_seek( struct Vgm_Emu* this, long msec ); + +// Skip n samples +blargg_err_t Track_skip( struct Vgm_Emu* this, long n ); + +// Set start time and length of track fade out. Once fade ends track_ended() returns +// true. Fade time can be changed while track is playing. +void Track_set_fade( struct Vgm_Emu* this, long start_msec, long length_msec ); + +// Get track length in milliseconds +static inline long Track_get_length( struct Vgm_Emu* this ) +{ + long length = this->info.length; + if ( length <= 0 ) + { + length = this->info.intro_length + 2 * this->info.loop_length; // intro + 2 loops + if ( length <= 0 ) + length = 150 * 1000; // 2.5 minutes + } + + return length; +} + +// Sound customization + +// Adjust song tempo, where 1.0 = normal, 0.5 = half speed, 2.0 = double speed. +// Track length as returned by track_info() assumes a tempo of 1.0. +void Sound_set_tempo( struct Vgm_Emu* this, double t ); + +// Mute/unmute voice i, where voice 0 is first voice +void Sound_mute_voice( struct Vgm_Emu* this, int index, bool mute ); + +// Set muting state of all voices at once using a bit mask, where -1 mutes them all, +// 0 unmutes them all, 0x01 mutes just the first voice, etc. +void Sound_mute_voices( struct Vgm_Emu* this, int mask ); + +// Change overall output amplitude, where 1.0 results in minimal clamping. +// Must be called before set_sample_rate(). +static inline void Sound_set_gain( struct Vgm_Emu* this, double g ) +{ + assert( !this->sample_rate ); // you must set gain before setting sample rate + this->gain = g; +} + + +#endif diff --git a/apps/codecs/libgme/vrc7tone.h b/apps/codecs/libgme/vrc7tone.h new file mode 100644 index 0000000000..a256c80ba6 --- /dev/null +++ b/apps/codecs/libgme/vrc7tone.h @@ -0,0 +1,20 @@ +/* VRC7 TONES by okazaki@angel.ne.jp */ +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x33,0x01,0x09,0x0e,0x94,0x90,0x40,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x13,0x41,0x0f,0x0d,0xce,0xd3,0x43,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x01,0x12,0x1b,0x06,0xff,0xd2,0x00,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x61,0x61,0x1b,0x07,0xaf,0x63,0x20,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x22,0x21,0x1e,0x06,0xf0,0x76,0x08,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x66,0x21,0x15,0x00,0x93,0x94,0x20,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x21,0x61,0x1c,0x07,0x82,0x81,0x10,0x17,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x23,0x21,0x20,0x1f,0xc0,0x71,0x07,0x47,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x25,0x31,0x26,0x05,0x64,0x41,0x18,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x17,0x21,0x28,0x07,0xff,0x83,0x02,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x97,0x81,0x25,0x07,0xcf,0xc8,0x02,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x21,0x21,0x54,0x0f,0x80,0x7f,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x01,0x01,0x56,0x03,0xd3,0xb2,0x43,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x31,0x21,0x0c,0x03,0x82,0xc0,0x40,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x21,0x01,0x0c,0x03,0xd4,0xd3,0x40,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x07,0x21,0x14,0x00,0xee,0xf8,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x01,0x31,0x00,0x00,0xf8,0xf7,0xf8,0xf7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x25,0x11,0x00,0x00,0xf8,0xfa,0xf8,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 diff --git a/apps/codecs/libgme/ym2413_emu.c b/apps/codecs/libgme/ym2413_emu.c new file mode 100644 index 0000000000..67870f31dc --- /dev/null +++ b/apps/codecs/libgme/ym2413_emu.c @@ -0,0 +1,45 @@ +// Game_Music_Emu 0.5.5. http://www.slack.net/~ant/ + +#include "ym2413_emu.h" + +void Ym2413_init( struct Ym2413_Emu* this ) +{ + this->last_time = disabled_time; this->out = 0; +} + +int Ym2413_set_rate( struct Ym2413_Emu* this, double sample_rate, double clock_rate ) +{ + OPLL_new ( &this->opll, clock_rate, sample_rate ); + OPLL_reset_patch( &this->opll, OPLL_2413_TONE ); + + Ym2413_reset( this ); + return 0; +} + +void Ym2413_reset( struct Ym2413_Emu* this ) +{ + OPLL_reset( &this->opll ); + OPLL_setMask( &this->opll, 0 ); +} + +void Ym2413_write( struct Ym2413_Emu* this, int addr, int data ) +{ + OPLL_writeIO( &this->opll, 0, addr ); + OPLL_writeIO( &this->opll, 1, data ); +} + +void Ym2413_mute_voices( struct Ym2413_Emu* this, int mask ) +{ + OPLL_setMask( &this->opll, mask ); +} + +void Ym2413_run( struct Ym2413_Emu* this, int pair_count, short* out ) +{ + while ( pair_count-- ) + { + int s = OPLL_calc( &this->opll ) << 1; + out [0] = s; + out [1] = s; + out += 2; + } +} diff --git a/apps/codecs/libgme/ym2413_emu.h b/apps/codecs/libgme/ym2413_emu.h new file mode 100644 index 0000000000..71369e9c88 --- /dev/null +++ b/apps/codecs/libgme/ym2413_emu.h @@ -0,0 +1,61 @@ +// YM2413 FM sound chip emulator interface + +// Game_Music_Emu 0.6-pre +#ifndef YM2413_EMU_H +#define YM2413_EMU_H + +#include "blargg_common.h" +#include "emu2413.h" + +enum { out_chan_count = 2 }; // stereo +enum { channel_count = 14 }; +enum { disabled_time = -1 }; + +struct Ym2413_Emu { + OPLL opll; + + // Impl + int last_time; + short* out; +}; + +void Ym2413_init( struct Ym2413_Emu* this ); + +static inline bool Ym2413_supported( void ) { return true; } + +// Sets output sample rate and chip clock rates, in Hz. Returns non-zero +// if error. +int Ym2413_set_rate( struct Ym2413_Emu* this, double sample_rate, double clock_rate ); + +// Resets to power-up state +void Ym2413_reset( struct Ym2413_Emu* this ); + +// Mutes voice n if bit n (1 << n) of mask is set +void Ym2413_mute_voices( struct Ym2413_Emu* this, int mask ); + +// Writes data to addr +void Ym2413_write( struct Ym2413_Emu* this, int addr, int data ) ICODE_ATTR; + +// Runs and writes pair_count*2 samples to output +void Ym2413_run( struct Ym2413_Emu* this, int pair_count, short* out ) ICODE_ATTR; + +static inline void Ym2413_enable( struct Ym2413_Emu* this, bool b ) { this->last_time = b ? 0 : disabled_time; } +static inline bool Ym2413_enabled( struct Ym2413_Emu* this ) { return this->last_time != disabled_time; } +static inline void Ym2413_begin_frame( struct Ym2413_Emu* this, short* buf ) { this->out = buf; this->last_time = 0; } + +static inline int Ym2413_run_until( struct Ym2413_Emu* this, int time ) +{ + int count = time - this->last_time; + if ( count > 0 ) + { + if ( this->last_time < 0 ) + return false; + this->last_time = time; + short* p = this->out; + this->out += count * out_chan_count; + Ym2413_run( this, count, p ); + } + return true; +} + +#endif diff --git a/apps/codecs/libgme/ym2612_emu.c b/apps/codecs/libgme/ym2612_emu.c new file mode 100644 index 0000000000..a2f32d30ca --- /dev/null +++ b/apps/codecs/libgme/ym2612_emu.c @@ -0,0 +1,1359 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +// Based on Gens 2.10 ym2612.c + +#include "ym2612_emu.h" + +#include +#include +#include +#include +#include +#include + +/* Copyright (C) 2002 Stéphane Dallongeville (gens AT consolemul.com) */ +/* Copyright (C) 2004-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +// This is mostly the original source in its C style and all. +// +// Somewhat optimized and simplified. Uses a template to generate the many +// variants of Update_Chan. Rewrote header file. In need of full rewrite by +// someone more familiar with FM sound and the YM2612. Has some inaccuracies +// compared to the Sega Genesis sound, particularly being mixed at such a +// high sample accuracy (the Genesis sounds like it has only 8 bit samples). +// - Shay + +// Ported again to c by gama. +// Not sure if performance is better than the original c version. + +#if !defined(ROCKBOX) + #define YM2612_CALCUL_TABLES +#else + #include "ymtables.h" +#endif + +const int output_bits = 14; + +static const unsigned char DT_DEF_TAB [4 * 32] ICONST_ATTR = +{ +// FD = 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + +// FD = 1 + 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 8, 8, + +// FD = 2 + 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, + 5, 6, 6, 7, 8, 8, 9, 10, 11, 12, 13, 14, 16, 16, 16, 16, + +// FD = 3 + 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, + 8 , 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 20, 22, 22, 22, 22 +}; + +static const unsigned char FKEY_TAB [16] ICONST_ATTR = +{ + 0, 0, 0, 0, + 0, 0, 0, 1, + 2, 3, 3, 3, + 3, 3, 3, 3 +}; + +static const unsigned char LFO_AMS_TAB [4] ICONST_ATTR = +{ + 31, 4, 1, 0 +}; + +static const unsigned char LFO_FMS_TAB [8] ICONST_ATTR = +{ + LFO_FMS_BASE * 0, LFO_FMS_BASE * 1, + LFO_FMS_BASE * 2, LFO_FMS_BASE * 3, + LFO_FMS_BASE * 4, LFO_FMS_BASE * 6, + LFO_FMS_BASE * 12, LFO_FMS_BASE * 24 +}; + +int in0, in1, in2, in3; // current phase calculation +// int en0, en1, en2, en3; // current enveloppe calculation + +inline void set_seg( struct slot_t* s, int seg ) +{ + s->env_xor = 0; + s->env_max = INT_MAX; + s->SEG = seg; + if ( seg & 4 ) + { + s->env_xor = ENV_MASK; + s->env_max = ENV_MASK; + } +} + +inline void YM2612_Special_Update(void) { } + +void KEY_ON( struct channel_* ch, struct tables_t *g, int nsl ) +{ + struct slot_t *SL = &(ch->SLOT [nsl]); // on recupere le bon pointeur de slot + + if (SL->Ecurp == RELEASE) // la touche est-elle rel'chee ? + { + SL->Fcnt = 0; + + // Fix Ecco 2 splash sound + + SL->Ecnt = (g->DECAY_TO_ATTACK [g->ENV_TAB [SL->Ecnt >> ENV_LBITS]] + ENV_ATTACK) & SL->ChgEnM; + SL->ChgEnM = ~0; + +// SL->Ecnt = g.DECAY_TO_ATTACK [g.ENV_TAB [SL->Ecnt >> ENV_LBITS]] + ENV_ATTACK; +// SL->Ecnt = 0; + + SL->Einc = SL->EincA; + SL->Ecmp = ENV_DECAY; + SL->Ecurp = ATTACK; + } +} + + +void KEY_OFF( struct channel_* ch, struct tables_t *g, int nsl ) +{ + struct slot_t *SL = &(ch->SLOT [nsl]); // on recupere le bon pointeur de slot + + if (SL->Ecurp != RELEASE) // la touche est-elle appuyee ? + { + if (SL->Ecnt < ENV_DECAY) // attack phase ? + { + SL->Ecnt = (g->ENV_TAB [SL->Ecnt >> ENV_LBITS] << ENV_LBITS) + ENV_DECAY; + } + + SL->Einc = SL->EincR; + SL->Ecmp = ENV_END; + SL->Ecurp = RELEASE; + } +} + + +int SLOT_SET( struct Ym2612_Impl* impl, int Adr, int data ) +{ + int nch = Adr & 3; + if ( nch == 3 ) + return 1; + + struct tables_t *g = &impl->g; + struct state_t *YM2612 = &impl->YM2612; + struct channel_* ch = &YM2612->CHANNEL [nch + (Adr & 0x100 ? 3 : 0)]; + struct slot_t* sl = &ch->SLOT [(Adr >> 2) & 3]; + + switch ( Adr & 0xF0 ) + { + case 0x30: + if ( (sl->MUL = (data & 0x0F)) != 0 ) sl->MUL <<= 1; + else sl->MUL = 1; + + sl->DT = (int*) g->DT_TAB [(data >> 4) & 7]; + + ch->SLOT [0].Finc = -1; + + break; + + case 0x40: + sl->TL = data & 0x7F; + + // SOR2 do a lot of TL adjustement and this fix R.Shinobi jump sound... + YM2612_Special_Update(); + +#if ((ENV_HBITS - 7) < 0) + sl->TLL = sl->TL >> (7 - ENV_HBITS); +#else + sl->TLL = sl->TL << (ENV_HBITS - 7); +#endif + + break; + + case 0x50: + sl->KSR_S = 3 - (data >> 6); + + ch->SLOT [0].Finc = -1; + + if (data &= 0x1F) sl->AR = (int*) &g->AR_TAB [data << 1]; + else sl->AR = (int*) &g->NULL_RATE [0]; + + sl->EincA = sl->AR [sl->KSR]; + if (sl->Ecurp == ATTACK) sl->Einc = sl->EincA; + break; + + case 0x60: + if ( (sl->AMSon = (data & 0x80)) != 0 ) sl->AMS = ch->AMS; + else sl->AMS = 31; + + if (data &= 0x1F) sl->DR = (int*) &g->DR_TAB [data << 1]; + else sl->DR = (int*) &g->NULL_RATE [0]; + + sl->EincD = sl->DR [sl->KSR]; + if (sl->Ecurp == DECAY) sl->Einc = sl->EincD; + break; + + case 0x70: + if (data &= 0x1F) sl->SR = (int*) &g->DR_TAB [data << 1]; + else sl->SR = (int*) &g->NULL_RATE [0]; + + sl->EincS = sl->SR [sl->KSR]; + if ((sl->Ecurp == SUBSTAIN) && (sl->Ecnt < ENV_END)) sl->Einc = sl->EincS; + break; + + case 0x80: + sl->SLL = g->SL_TAB [data >> 4]; + + sl->RR = (int*) &g->DR_TAB [((data & 0xF) << 2) + 2]; + + sl->EincR = sl->RR [sl->KSR]; + if ((sl->Ecurp == RELEASE) && (sl->Ecnt < ENV_END)) sl->Einc = sl->EincR; + break; + + case 0x90: + // SSG-EG envelope shapes : + /* + E At Al H + + 1 0 0 0 \\\\ + 1 0 0 1 \___ + 1 0 1 0 \/\/ + 1 0 1 1 \ + 1 1 0 0 //// + 1 1 0 1 / + 1 1 1 0 /\/\ + 1 1 1 1 /___ + + E = SSG-EG enable + At = Start negate + Al = Altern + H = Hold */ + + set_seg( sl, (data & 8) ? (data & 0x0F) : 0 ); + break; + } + + return 0; +} + + +int CHANNEL_SET( struct state_t* YM2612, int Adr, int data ) +{ + int num = Adr & 3; + if ( num == 3 ) + return 1; + + struct channel_* ch = &YM2612->CHANNEL [num + (Adr & 0x100 ? 3 : 0)]; + + switch ( Adr & 0xFC ) + { + case 0xA0: + YM2612_Special_Update(); + + ch->FNUM [0] = (ch->FNUM [0] & 0x700) + data; + ch->KC [0] = (ch->FOCT [0] << 2) | FKEY_TAB [ch->FNUM [0] >> 7]; + + ch->SLOT [0].Finc = -1; + break; + + case 0xA4: + YM2612_Special_Update(); + + ch->FNUM [0] = (ch->FNUM [0] & 0x0FF) + ((data & 0x07) << 8); + ch->FOCT [0] = (data & 0x38) >> 3; + ch->KC [0] = (ch->FOCT [0] << 2) | FKEY_TAB [ch->FNUM [0] >> 7]; + + ch->SLOT [0].Finc = -1; + break; + + case 0xA8: + if ( Adr < 0x100 ) + { + num++; + + YM2612_Special_Update(); + + YM2612->CHANNEL [2].FNUM [num] = (YM2612->CHANNEL [2].FNUM [num] & 0x700) + data; + YM2612->CHANNEL [2].KC [num] = (YM2612->CHANNEL [2].FOCT [num] << 2) | + FKEY_TAB [YM2612->CHANNEL [2].FNUM [num] >> 7]; + + YM2612->CHANNEL [2].SLOT [0].Finc = -1; + } + break; + + case 0xAC: + if ( Adr < 0x100 ) + { + num++; + + YM2612_Special_Update(); + + YM2612->CHANNEL [2].FNUM [num] = (YM2612->CHANNEL [2].FNUM [num] & 0x0FF) + ((data & 0x07) << 8); + YM2612->CHANNEL [2].FOCT [num] = (data & 0x38) >> 3; + YM2612->CHANNEL [2].KC [num] = (YM2612->CHANNEL [2].FOCT [num] << 2) | + FKEY_TAB [YM2612->CHANNEL [2].FNUM [num] >> 7]; + + YM2612->CHANNEL [2].SLOT [0].Finc = -1; + } + break; + + case 0xB0: + if ( ch->ALGO != (data & 7) ) + { + // Fix VectorMan 2 heli sound (level 1) + YM2612_Special_Update(); + + ch->ALGO = data & 7; + + ch->SLOT [0].ChgEnM = 0; + ch->SLOT [1].ChgEnM = 0; + ch->SLOT [2].ChgEnM = 0; + ch->SLOT [3].ChgEnM = 0; + } + + ch->FB = 9 - ((data >> 3) & 7); // Real thing ? + +// if (ch->FB = ((data >> 3) & 7)) ch->FB = 9 - ch->FB; // Thunder force 4 (music stage 8), Gynoug, Aladdin bug sound... +// else ch->FB = 31; + break; + + case 0xB4: { + YM2612_Special_Update(); + + ch->LEFT = 0 - ((data >> 7) & 1); + ch->RIGHT = 0 - ((data >> 6) & 1); + + ch->AMS = LFO_AMS_TAB [(data >> 4) & 3]; + ch->FMS = LFO_FMS_TAB [data & 7]; + + int i; + for ( i = 0; i < 4; i++ ) + { + struct slot_t* sl = &ch->SLOT [i]; + sl->AMS = (sl->AMSon ? ch->AMS : 31); + } + break; + } + } + + return 0; +} + + +int YM_SET( struct Ym2612_Impl* impl, int Adr, int data ) +{ + struct state_t* YM2612 = &impl->YM2612; + struct tables_t* g = &impl->g; + switch ( Adr ) + { + case 0x22: + if (data & 8) // LFO enable + { + // Cool Spot music 1, LFO modified severals time which + // distord the sound, have to check that on a real genesis... + + g->LFOinc = g->LFO_INC_TAB [data & 7]; + } + else + { + g->LFOinc = g->LFOcnt = 0; + } + break; + + case 0x24: + YM2612->TimerA = (YM2612->TimerA & 0x003) | (((int) data) << 2); + + if (YM2612->TimerAL != (1024 - YM2612->TimerA) << 12) + { + YM2612->TimerAcnt = YM2612->TimerAL = (1024 - YM2612->TimerA) << 12; + } + break; + + case 0x25: + YM2612->TimerA = (YM2612->TimerA & 0x3FC) | (data & 3); + + if (YM2612->TimerAL != (1024 - YM2612->TimerA) << 12) + { + YM2612->TimerAcnt = YM2612->TimerAL = (1024 - YM2612->TimerA) << 12; + } + break; + + case 0x26: + YM2612->TimerB = data; + + if (YM2612->TimerBL != (256 - YM2612->TimerB) << (4 + 12)) + { + YM2612->TimerBcnt = YM2612->TimerBL = (256 - YM2612->TimerB) << (4 + 12); + } + break; + + case 0x27: + // Parametre divers + // b7 = CSM MODE + // b6 = 3 slot mode + // b5 = reset b + // b4 = reset a + // b3 = timer enable b + // b2 = timer enable a + // b1 = load b + // b0 = load a + + if ((data ^ YM2612->Mode) & 0x40) + { + // We changed the channel 2 mode, so recalculate phase step + // This fix the punch sound in Street of Rage 2 + + YM2612_Special_Update(); + + YM2612->CHANNEL [2].SLOT [0].Finc = -1; // recalculate phase step + } + +// if ((data & 2) && (YM2612->Status & 2)) YM2612->TimerBcnt = YM2612->TimerBL; +// if ((data & 1) && (YM2612->Status & 1)) YM2612->TimerAcnt = YM2612->TimerAL; + +// YM2612->Status &= (~data >> 4); // Reset du Status au cas ou c'est demande + YM2612->Status &= (~data >> 4) & (data >> 2); // Reset Status + + YM2612->Mode = data; + break; + + case 0x28: { + int nch = data & 3; + if ( nch == 3 ) + return 1; + if ( data & 4 ) + nch += 3; + struct channel_* ch = &YM2612->CHANNEL [nch]; + + YM2612_Special_Update(); + + if (data & 0x10) KEY_ON(ch, g, S0); // On appuie sur la touche pour le slot 1 + else KEY_OFF(ch, g, S0); // On rel'che la touche pour le slot 1 + if (data & 0x20) KEY_ON(ch, g, S1); // On appuie sur la touche pour le slot 3 + else KEY_OFF(ch, g, S1); // On rel'che la touche pour le slot 3 + if (data & 0x40) KEY_ON(ch, g, S2); // On appuie sur la touche pour le slot 2 + else KEY_OFF(ch, g, S2); // On rel'che la touche pour le slot 2 + if (data & 0x80) KEY_ON(ch, g, S3); // On appuie sur la touche pour le slot 4 + else KEY_OFF(ch, g, S3); // On rel'che la touche pour le slot 4 + break; + } + + case 0x2B: + if (YM2612->DAC ^ (data & 0x80)) YM2612_Special_Update(); + + YM2612->DAC = data & 0x80; // activation/desactivation du DAC + break; + } + + return 0; +} + +#if defined(ROCKBOX) +double fabs(double x) +{ + if (x < 0.0) return -x; + return x; +} + +double ipow(double a,int b) +{ + if (b < 0) { + a = 1.0 / a; + b = -b; + } + double result = 1.0; + while(b) { + if (b & 1) result*=a; + a *= a; + b >>= 1; + } + return result; +} +#endif + +void impl_reset( struct Ym2612_Impl* impl ); +void impl_set_rate( struct Ym2612_Impl* impl, double sample_rate, double clock_rate ) +{ + assert( sample_rate ); + assert( !clock_rate || clock_rate > sample_rate ); + + int i; + + // 144 = 12 * (prescale * 2) = 12 * 6 * 2 + // prescale set to 6 by default + + double Frequence = (clock_rate ? clock_rate / sample_rate / 144.0 : 1.0); + if ( fabs( Frequence - 1.0 ) < 0.0000001 ) + Frequence = 1.0; + impl->YM2612.TimerBase = (int) (Frequence * 4096.0); + + // Tableau TL : + // [0 - 4095] = +output [4095 - ...] = +output overflow (fill with 0) + // [12288 - 16383] = -output [16384 - ...] = -output overflow (fill with 0) + + for ( i = 0; i < TL_LENGHT; i++ ) + { + if (i >= PG_CUT_OFF) // YM2612 cut off sound after 78 dB (14 bits output ?) + { + impl->g.TL_TAB [TL_LENGHT + i] = impl->g.TL_TAB [i] = 0; + } + else + { + // Decibel -> Voltage + #ifdef YM2612_CALCUL_TABLES + impl->g.TL_TAB [i] = (int) (MAX_OUT / pow( 10.0, ENV_STEP / 20.0f * i )); + #else + impl->g.TL_TAB [i] = tl_coeff [i]; + #endif + impl->g.TL_TAB [TL_LENGHT + i] = -impl->g.TL_TAB [i]; + } + } + + // Tableau SIN : + // impl->g.SIN_TAB [x] [y] = sin(x) * y; + // x = phase and y = volume + + impl->g.SIN_TAB [0] = impl->g.SIN_TAB [SIN_LENGHT / 2] = PG_CUT_OFF; + + for ( i = 1; i <= SIN_LENGHT / 4; i++ ) + { + // Sinus in dB + #ifdef YM2612_CALCUL_TABLES + double x = 20 * log10( 1 / sin( 2.0 * PI * i / SIN_LENGHT ) ); // convert to dB + + int j = (int) (x / ENV_STEP); // Get TL range + + if (j > PG_CUT_OFF) j = (int) PG_CUT_OFF; + #else + int j = sindb_coeff [i-1]; + #endif + + impl->g.SIN_TAB [i] = impl->g.SIN_TAB [(SIN_LENGHT / 2) - i] = j; + impl->g.SIN_TAB [(SIN_LENGHT / 2) + i] = impl->g.SIN_TAB [SIN_LENGHT - i] = TL_LENGHT + j; + } + + // Tableau LFO (LFO wav) : + + for ( i = 0; i < LFO_LENGHT; i++ ) + { + #ifdef YM2612_CALCUL_TABLES + double x = 1 + sin( 2.0 * PI * i * (1.0 / LFO_LENGHT) ); // Sinus + x *= 11.8 / ENV_STEP / 2; // ajusted to MAX enveloppe modulation + + impl->g.LFO_ENV_TAB [i] = (int) x; + + x = sin( 2.0 * PI * i * (1.0 / LFO_LENGHT) ); // Sinus + x *= (1 << (LFO_HBITS - 1)) - 1; + + impl->g.LFO_FREQ_TAB [i] = (int) x; + #else + impl->g.LFO_ENV_TAB [i] = lfo_env_coeff [i]; + impl->g.LFO_FREQ_TAB [i] = lfo_freq_coeff [i]; + #endif + } + + // Tableau Enveloppe : + // impl->g.ENV_TAB [0] -> impl->g.ENV_TAB [ENV_LENGHT - 1] = attack curve + // impl->g.ENV_TAB [ENV_LENGHT] -> impl->g.ENV_TAB [2 * ENV_LENGHT - 1] = decay curve + + for ( i = 0; i < ENV_LENGHT; i++ ) + { + // Attack curve (x^8 - music level 2 Vectorman 2) + #if defined(ROCKBOX) + double x = ipow( ((ENV_LENGHT - 1) - i) / (double) ENV_LENGHT, 8.0 ); + #else + double x = pow( ((ENV_LENGHT - 1) - i) / (double) ENV_LENGHT, 8.0 ); + #endif + x *= ENV_LENGHT; + + impl->g.ENV_TAB [i] = (int) x; + + // Decay curve (just linear) + impl->g.ENV_TAB [ENV_LENGHT + i] = i; + } + for ( i = 0; i < 8; i++ ) + impl->g.ENV_TAB [i + ENV_LENGHT * 2] = 0; + + impl->g.ENV_TAB [ENV_END >> ENV_LBITS] = ENV_LENGHT - 1; // for the stopped state + + // Tableau pour la conversion Attack -> Decay and Decay -> Attack + + int j = ENV_LENGHT - 1; + for ( i = 0; i < ENV_LENGHT; i++ ) + { + while ( j && impl->g.ENV_TAB [j] < i ) + j--; + + impl->g.DECAY_TO_ATTACK [i] = j << ENV_LBITS; + } + + // Tableau pour le Substain Level + + for ( i = 0; i < 15; i++ ) + { + double x = i * 3 / ENV_STEP; // 3 and not 6 (Mickey Mania first music for test) + + impl->g.SL_TAB [i] = ((int) x << ENV_LBITS) + ENV_DECAY; + } + + impl->g.SL_TAB [15] = ((ENV_LENGHT - 1) << ENV_LBITS) + ENV_DECAY; // special case : volume off + + // Tableau Frequency Step + { + // 0.5 because MUL = value * 2 + #if SIN_LBITS + SIN_HBITS - (21 - 7) < 0 + double const factor = 0.5 / (1 << ((21 - 7) - SIN_LBITS - SIN_HBITS)) * Frequence; + #else + double const factor = 0.5 * (1 << (SIN_LBITS + SIN_HBITS - (21 - 7))) * Frequence; + #endif + for ( i = 0; i < 2048; i++ ) + impl->g.FINC_TAB [i] = (unsigned) (i * factor); + } + + // Tableaux Attack & Decay Rate + + for ( i = 0; i < 4; i++ ) + { + impl->g.AR_TAB [i] = 0; + impl->g.DR_TAB [i] = 0; + } + + for ( i = 0; i < 60; i++ ) + { + double x = + (1.0 + ((i & 3) * 0.25)) * // bits 0-1 : x1.00, x1.25, x1.50, x1.75 + (ENV_LENGHT << ENV_LBITS) * // on ajuste pour le tableau impl->g.ENV_TAB + Frequence * + (1 << (i >> 2)); // bits 2-5 : shift bits (x2^0 - x2^15) + + impl->g.AR_TAB [i + 4] = (unsigned int) (x / AR_RATE); + impl->g.DR_TAB [i + 4] = (unsigned int) (x / DR_RATE); + } + + for ( i = 64; i < 96; i++ ) + { + impl->g.AR_TAB [i] = impl->g.AR_TAB [63]; + impl->g.DR_TAB [i] = impl->g.DR_TAB [63]; + + impl->g.NULL_RATE [i - 64] = 0; + } + + for ( i = 96; i < 128; i++ ) + impl->g.AR_TAB [i] = 0; + + // Tableau Detune + { + #if SIN_LBITS + SIN_HBITS - 21 < 0 + double const factor = 1.0 / (1 << (21 - SIN_LBITS - SIN_HBITS)) * Frequence; + #else + double const factor = (1 << (SIN_LBITS + SIN_HBITS - 21)) * Frequence; + #endif + for ( i = 0; i < 4; i++ ) + { + int j; + for ( j = 0; j < 32; j++ ) + { + double y = DT_DEF_TAB [(i << 5) + j] * factor; + + impl->g.DT_TAB [i + 0] [j] = (int) y; + impl->g.DT_TAB [i + 4] [j] = (int) -y; + } + } + } + + // Tableau LFO + impl->g.LFO_INC_TAB [0] = (unsigned) (3.98 * (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + impl->g.LFO_INC_TAB [1] = (unsigned) (5.56 * (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + impl->g.LFO_INC_TAB [2] = (unsigned) (6.02 * (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + impl->g.LFO_INC_TAB [3] = (unsigned) (6.37 * (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + impl->g.LFO_INC_TAB [4] = (unsigned) (6.88 * (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + impl->g.LFO_INC_TAB [5] = (unsigned) (9.63 * (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + impl->g.LFO_INC_TAB [6] = (unsigned) (48.1 * (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + impl->g.LFO_INC_TAB [7] = (unsigned) (72.2 * (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + + impl_reset( impl ); +} + +const char* Ym2612_set_rate( struct Ym2612_Emu* this, double sample_rate, double clock_rate ) +{ +// Only set rates if necessary +#if defined(ROCKBOX) + static double last_sample_rate = 0.0, last_clock_rate = 0.0; + if (last_sample_rate == sample_rate && last_clock_rate == clock_rate) return 0; +#endif + memset( &this->impl.YM2612, 0, sizeof this->impl.YM2612 ); + impl_set_rate( &this->impl, sample_rate, clock_rate ); + + return 0; +} + +inline void write0( struct Ym2612_Impl* impl, int opn_addr, int data ) +{ + assert( (unsigned) data <= 0xFF ); + + if ( opn_addr < 0x30 ) + { + impl->YM2612.REG [0] [opn_addr] = data; + YM_SET( impl, opn_addr, data ); + } + else if ( impl->YM2612.REG [0] [opn_addr] != data ) + { + impl->YM2612.REG [0] [opn_addr] = data; + + if ( opn_addr < 0xA0 ) + SLOT_SET( impl, opn_addr, data ); + else + CHANNEL_SET( &impl->YM2612, opn_addr, data ); + } +} + +inline void write1( struct Ym2612_Impl* impl, int opn_addr, int data ) +{ + assert( (unsigned) data <= 0xFF ); + + if ( opn_addr >= 0x30 && impl->YM2612.REG [1] [opn_addr] != data ) + { + impl->YM2612.REG [1] [opn_addr] = data; + + if ( opn_addr < 0xA0 ) + SLOT_SET( impl, opn_addr + 0x100, data ); + else + CHANNEL_SET( &impl->YM2612, opn_addr + 0x100, data ); + } +} + +void impl_reset( struct Ym2612_Impl* impl ) +{ + impl->g.LFOcnt = 0; + impl->YM2612.TimerA = 0; + impl->YM2612.TimerAL = 0; + impl->YM2612.TimerAcnt = 0; + impl->YM2612.TimerB = 0; + impl->YM2612.TimerBL = 0; + impl->YM2612.TimerBcnt = 0; + impl->YM2612.DAC = 0; + + impl->YM2612.Status = 0; + + int i; + for ( i = 0; i < ym2612_channel_count; i++ ) + { + struct channel_* ch = &impl->YM2612.CHANNEL [i]; + + ch->LEFT = ~0; + ch->RIGHT = ~0; + ch->ALGO = 0; + ch->FB = 31; + ch->FMS = 0; + ch->AMS = 0; + + int j; + for ( j = 0 ;j < 4 ; j++ ) + { + ch->S0_OUT [j] = 0; + ch->FNUM [j] = 0; + ch->FOCT [j] = 0; + ch->KC [j] = 0; + + ch->SLOT [j].Fcnt = 0; + ch->SLOT [j].Finc = 0; + ch->SLOT [j].Ecnt = ENV_END; // Put it at the end of Decay phase... + ch->SLOT [j].Einc = 0; + ch->SLOT [j].Ecmp = 0; + ch->SLOT [j].Ecurp = RELEASE; + + ch->SLOT [j].ChgEnM = 0; + } + } + + for ( i = 0; i < 0x100; i++ ) + { + impl->YM2612.REG [0] [i] = -1; + impl->YM2612.REG [1] [i] = -1; + } + + for ( i = 0xB6; i >= 0xB4; i-- ) + { + write0( impl, i, 0xC0 ); + write1( impl, i, 0xC0 ); + } + + for ( i = 0xB2; i >= 0x22; i-- ) + { + write0( impl, i, 0 ); + write1( impl, i, 0 ); + } + + write0( impl, 0x2A, 0x80 ); +} + +void Ym2612_reset( struct Ym2612_Emu* this ) +{ + impl_reset( &this->impl ); +} + +void Ym2612_write0( struct Ym2612_Emu* this, int addr, int data ) +{ + write0( &this->impl, addr, data ); +} + +void Ym2612_write1( struct Ym2612_Emu* this, int addr, int data ) +{ + write1( &this->impl, addr, data ); +} + +void Ym2612_mute_voices( struct Ym2612_Emu* this, int mask ) { this->impl.mute_mask = mask; } + +static void update_envelope_( struct slot_t* sl ) +{ + switch ( sl->Ecurp ) + { + case 0: + // Env_Attack_Next + + // Verified with Gynoug even in HQ (explode SFX) + sl->Ecnt = ENV_DECAY; + + sl->Einc = sl->EincD; + sl->Ecmp = sl->SLL; + sl->Ecurp = DECAY; + break; + + case 1: + // Env_Decay_Next + + // Verified with Gynoug even in HQ (explode SFX) + sl->Ecnt = sl->SLL; + + sl->Einc = sl->EincS; + sl->Ecmp = ENV_END; + sl->Ecurp = SUBSTAIN; + break; + + case 2: + // Env_Substain_Next(slot_t *SL) + if (sl->SEG & 8) // SSG envelope type + { + int release = sl->SEG & 1; + + if ( !release ) + { + // re KEY ON + + // sl->Fcnt = 0; + // sl->ChgEnM = ~0; + + sl->Ecnt = 0; + sl->Einc = sl->EincA; + sl->Ecmp = ENV_DECAY; + sl->Ecurp = ATTACK; + } + + set_seg( sl, (sl->SEG << 1) & 4 ); + + if ( !release ) + break; + } + // fall through + + case 3: + // Env_Release_Next + sl->Ecnt = ENV_END; + sl->Einc = 0; + sl->Ecmp = ENV_END + 1; + break; + + // default: no op + } +} + +static inline void update_envelope( struct slot_t* sl ) +{ + int ecmp = sl->Ecmp; + if ( (sl->Ecnt += sl->Einc) >= ecmp ) + update_envelope_( sl ); +} + + +typedef void (*ym2612_update_chan_t)( struct tables_t*, struct channel_*, short*, int ); + +#define GET_CURRENT_PHASE \ +int in0 = ch->SLOT[S0].Fcnt; \ +int in1 = ch->SLOT[S1].Fcnt; \ +int in2 = ch->SLOT[S2].Fcnt; \ +int in3 = ch->SLOT[S3].Fcnt; \ + +#define GET_CURRENT_LFO \ +int YM2612_LFOinc = g->LFOinc; \ +int YM2612_LFOcnt = g->LFOcnt + YM2612_LFOinc; + +#define CALC_EN( x ) \ + int temp##x = ENV_TAB [ch->SLOT [S##x].Ecnt >> ENV_LBITS] + ch->SLOT [S##x].TLL; \ + int en##x = ((temp##x ^ ch->SLOT [S##x].env_xor) + (env_LFO >> ch->SLOT [S##x].AMS)) & \ + ((temp##x - ch->SLOT [S##x].env_max) >> 31); + +#define GET_ENV \ +int const env_LFO = g->LFO_ENV_TAB [YM2612_LFOcnt >> LFO_LBITS & LFO_MASK]; \ +short const* const ENV_TAB = g->ENV_TAB; \ +CALC_EN( 0 ) \ +CALC_EN( 1 ) \ +CALC_EN( 2 ) \ +CALC_EN( 3 ) \ +int const* const TL_TAB = g->TL_TAB; + +#define DO_FEEDBACK \ +int CH_S0_OUT_0 = ch->S0_OUT [0]; \ +{ \ + int temp = in0 + ((CH_S0_OUT_0 + CH_S0_OUT_1) >> ch->FB); \ + CH_S0_OUT_1 = CH_S0_OUT_0; \ + CH_S0_OUT_0 = SINT( (temp >> SIN_LBITS) & SIN_MASK, en0 ); \ +} \ + +#define SINT( i, o ) (TL_TAB [g->SIN_TAB [(i)] + (o)]) + +#define DO_LIMIT \ +CH_OUTd >>= MAX_OUT_BITS - output_bits + 2; \ + +#define UPDATE_PHASE_CYCLE \ +unsigned freq_LFO = ((g->LFO_FREQ_TAB [YM2612_LFOcnt >> LFO_LBITS & LFO_MASK] * \ + ch->FMS) >> (LFO_HBITS - 1 + 1)) + (1 << (LFO_FMS_LBITS - 1)); \ +YM2612_LFOcnt += YM2612_LFOinc; \ +in0 += (ch->SLOT [S0].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); \ +in1 += (ch->SLOT [S1].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); \ +in2 += (ch->SLOT [S2].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); \ +in3 += (ch->SLOT [S3].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + +#define UPDATE_ENV \ +int t0 = buf [0] + (CH_OUTd & ch->LEFT); \ +int t1 = buf [1] + (CH_OUTd & ch->RIGHT); \ +update_envelope( &ch->SLOT [0] ); \ +update_envelope( &ch->SLOT [1] ); \ +update_envelope( &ch->SLOT [2] ); \ +update_envelope( &ch->SLOT [3] ); + +#define DO_OUTPUT_0 \ +ch->S0_OUT [0] = CH_S0_OUT_0; \ +buf [0] = t0; \ +buf [1] = t1; \ +buf += 2; \ + +#define DO_OUTPUT_1 \ +ch->S0_OUT [1] = CH_S0_OUT_1; + +#define UPDATE_PHASE \ +ch->SLOT [S0].Fcnt = in0; \ +ch->SLOT [S1].Fcnt = in1; \ +ch->SLOT [S2].Fcnt = in2; \ +ch->SLOT [S3].Fcnt = in3; + +void ym2612_update_chan0( struct tables_t* g, struct channel_* ch, + short* buf, int length ) +{ + int not_end = ch->SLOT [S3].Ecnt - ENV_END; + int CH_S0_OUT_1 = ch->S0_OUT [1]; + + GET_CURRENT_PHASE + GET_CURRENT_LFO + + if ( !not_end ) + return; + + do + { + GET_ENV + DO_FEEDBACK + + int CH_OUTd; + int temp = in1 + CH_S0_OUT_1; + temp = in2 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en1 ); + temp = in3 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + + DO_LIMIT + UPDATE_PHASE_CYCLE + UPDATE_ENV + DO_OUTPUT_0 + } + while ( --length ); + DO_OUTPUT_1 + UPDATE_PHASE +} + +void ym2612_update_chan1( struct tables_t* g, struct channel_* ch, + short* buf, int length ) +{ + int not_end = ch->SLOT [S3].Ecnt - ENV_END; + int CH_S0_OUT_1 = ch->S0_OUT [1]; + + GET_CURRENT_PHASE + GET_CURRENT_LFO + + if ( !not_end ) + return; + + do + { + GET_ENV + DO_FEEDBACK + + int CH_OUTd; + int temp = in2 + CH_S0_OUT_1 + SINT( (in1 >> SIN_LBITS) & SIN_MASK, en1 ); + temp = in3 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + + DO_LIMIT + UPDATE_PHASE_CYCLE + UPDATE_ENV + DO_OUTPUT_0 + } + while ( --length ); + DO_OUTPUT_1 + UPDATE_PHASE +} + +void ym2612_update_chan2( struct tables_t* g, struct channel_* ch, + short* buf, int length ) +{ + int not_end = ch->SLOT [S3].Ecnt - ENV_END; + int CH_S0_OUT_1 = ch->S0_OUT [1]; + + GET_CURRENT_PHASE + GET_CURRENT_LFO + + if ( !not_end ) + return; + + do + { + GET_ENV + DO_FEEDBACK + + int CH_OUTd; + int temp = in2 + SINT( (in1 >> SIN_LBITS) & SIN_MASK, en1 ); + temp = in3 + CH_S0_OUT_1 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + + DO_LIMIT + UPDATE_PHASE_CYCLE + UPDATE_ENV + DO_OUTPUT_0 + } + while ( --length ); + DO_OUTPUT_1 + UPDATE_PHASE +} + +void ym2612_update_chan3( struct tables_t* g, struct channel_* ch, + short* buf, int length ) +{ + int not_end = ch->SLOT [S3].Ecnt - ENV_END; + int CH_S0_OUT_1 = ch->S0_OUT [1]; + + GET_CURRENT_PHASE + GET_CURRENT_LFO + + if ( !not_end ) + return; + + do + { + GET_ENV + DO_FEEDBACK + + int CH_OUTd; + int temp = in1 + CH_S0_OUT_1; + temp = in3 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + + DO_LIMIT + UPDATE_PHASE_CYCLE + UPDATE_ENV + DO_OUTPUT_0 + } + while ( --length ); + DO_OUTPUT_1 + UPDATE_PHASE +} + +void ym2612_update_chan4( struct tables_t* g, struct channel_* ch, + short* buf, int length ) +{ + int not_end = ch->SLOT [S3].Ecnt - ENV_END; + not_end |= ch->SLOT [S1].Ecnt - ENV_END; + + int CH_S0_OUT_1 = ch->S0_OUT [1]; + + GET_CURRENT_PHASE + GET_CURRENT_LFO + + if ( !not_end ) + return; + + do + { + GET_ENV + DO_FEEDBACK + + int CH_OUTd; + int temp = in3 + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( ((in1 + CH_S0_OUT_1) >> SIN_LBITS) & SIN_MASK, en1 ); + + DO_LIMIT + UPDATE_PHASE_CYCLE + UPDATE_ENV + DO_OUTPUT_0 + } + while ( --length ); + DO_OUTPUT_1 + UPDATE_PHASE +} + +void ym2612_update_chan5( struct tables_t* g, struct channel_* ch, + short* buf, int length ) +{ + int not_end = ch->SLOT [S3].Ecnt - ENV_END; + not_end |= ch->SLOT [S2].Ecnt - ENV_END; + not_end |= ch->SLOT [S1].Ecnt - ENV_END; + + int CH_S0_OUT_1 = ch->S0_OUT [1]; + + GET_CURRENT_PHASE + GET_CURRENT_LFO + + if ( !not_end ) + return; + + do + { + GET_ENV + DO_FEEDBACK + + int CH_OUTd; + int temp = CH_S0_OUT_1; + CH_OUTd = SINT( ((in3 + temp) >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( ((in1 + temp) >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( ((in2 + temp) >> SIN_LBITS) & SIN_MASK, en2 ); + + DO_LIMIT + UPDATE_PHASE_CYCLE + UPDATE_ENV + DO_OUTPUT_0 + } + while ( --length ); + DO_OUTPUT_1 + UPDATE_PHASE +} + +void ym2612_update_chan6( struct tables_t* g, struct channel_* ch, + short* buf, int length ) +{ + int not_end = ch->SLOT [S3].Ecnt - ENV_END; + not_end |= ch->SLOT [S2].Ecnt - ENV_END; + not_end |= ch->SLOT [S1].Ecnt - ENV_END; + + int CH_S0_OUT_1 = ch->S0_OUT [1]; + + GET_CURRENT_PHASE + GET_CURRENT_LFO + + if ( !not_end ) + return; + + do + { + GET_ENV + DO_FEEDBACK + + int CH_OUTd; + CH_OUTd = SINT( (in3 >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( ((in1 + CH_S0_OUT_1) >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ); + + DO_LIMIT + UPDATE_PHASE_CYCLE + UPDATE_ENV + DO_OUTPUT_0 + } + while ( --length ); + DO_OUTPUT_1 + UPDATE_PHASE +} + +void ym2612_update_chan7( struct tables_t* g, struct channel_* ch, + short* buf, int length ) +{ + int not_end = ch->SLOT [S3].Ecnt - ENV_END; + not_end |= ch->SLOT [S0].Ecnt - ENV_END; + not_end |= ch->SLOT [S2].Ecnt - ENV_END; + not_end |= ch->SLOT [S1].Ecnt - ENV_END; + + int CH_S0_OUT_1 = ch->S0_OUT [1]; + + GET_CURRENT_PHASE + GET_CURRENT_LFO + + if ( !not_end ) + return; + + do + { + GET_ENV + DO_FEEDBACK + + int CH_OUTd; + CH_OUTd = SINT( (in3 >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( (in1 >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ) + CH_S0_OUT_1; + + DO_LIMIT + UPDATE_PHASE_CYCLE + UPDATE_ENV + DO_OUTPUT_0 + } + while ( --length ); + DO_OUTPUT_1 + UPDATE_PHASE +} + +static void (*UPDATE_CHAN[8])(struct tables_t* g, struct channel_* ch, + short* buf, int length) = +{ + (void *)ym2612_update_chan0, + (void *)ym2612_update_chan1, + (void *)ym2612_update_chan2, + (void *)ym2612_update_chan3, + (void *)ym2612_update_chan4, + (void *)ym2612_update_chan5, + (void *)ym2612_update_chan6, + (void *)ym2612_update_chan7 +}; + +void run_timer( struct Ym2612_Impl* impl, int length ) +{ + int const step = 6; + int remain = length; + do + { + int n = step; + if ( n > remain ) + n = remain; + remain -= n; + + int i = n * impl->YM2612.TimerBase; + if (impl->YM2612.Mode & 1) // Timer A ON ? + { + // if ((impl->YM2612.TimerAcnt -= 14073) <= 0) // 13879=NTSC (old: 14475=NTSC 14586=PAL) + if ((impl->YM2612.TimerAcnt -= i) <= 0) + { + // timer a overflow + + impl->YM2612.Status |= (impl->YM2612.Mode & 0x04) >> 2; + impl->YM2612.TimerAcnt += impl->YM2612.TimerAL; + + if (impl->YM2612.Mode & 0x80) + { + KEY_ON( &impl->YM2612.CHANNEL [2], &impl->g, 0 ); + KEY_ON( &impl->YM2612.CHANNEL [2], &impl->g, 1 ); + KEY_ON( &impl->YM2612.CHANNEL [2], &impl->g, 2 ); + KEY_ON( &impl->YM2612.CHANNEL [2], &impl->g, 3 ); + } + } + } + + if (impl->YM2612.Mode & 2) // Timer B ON ? + { + // if ((impl->YM2612.TimerBcnt -= 14073) <= 0) // 13879=NTSC (old: 14475=NTSC 14586=PAL) + if ((impl->YM2612.TimerBcnt -= i) <= 0) + { + // timer b overflow + impl->YM2612.Status |= (impl->YM2612.Mode & 0x08) >> 2; + impl->YM2612.TimerBcnt += impl->YM2612.TimerBL; + } + } + } + while ( remain > 0 ); +} + +void impl_run( struct Ym2612_Impl* impl, int pair_count, short out [] ) +{ + if ( pair_count <= 0 ) + return; + + if ( impl->YM2612.Mode & 3 ) + run_timer( impl, pair_count ); + + // Mise à jour des pas des compteurs-frequences s'ils ont ete modifies + + int chi; + for ( chi = 0; chi < ym2612_channel_count; chi++ ) + { + struct channel_* ch = &impl->YM2612.CHANNEL [chi]; + if ( ch->SLOT [0].Finc != -1 ) + continue; + + int i2 = 0; + if ( chi == 2 && (impl->YM2612.Mode & 0x40) ) + i2 = 2; + + int i; + for ( i = 0; i < 4; i++ ) + { + // static int seq [4] = { 2, 1, 3, 0 }; + // if ( i2 ) i2 = seq [i]; + + struct slot_t* sl = &ch->SLOT [i]; + int finc = impl->g.FINC_TAB [ch->FNUM [i2]] >> (7 - ch->FOCT [i2]); + int ksr = ch->KC [i2] >> sl->KSR_S; // keycode attenuation + sl->Finc = (finc + sl->DT [ch->KC [i2]]) * sl->MUL; + if (sl->KSR != ksr) // si le KSR a change alors + { // les differents taux pour l'enveloppe sont mis à jour + sl->KSR = ksr; + + sl->EincA = sl->AR [ksr]; + sl->EincD = sl->DR [ksr]; + sl->EincS = sl->SR [ksr]; + sl->EincR = sl->RR [ksr]; + + if (sl->Ecurp == ATTACK) + { + sl->Einc = sl->EincA; + } + else if (sl->Ecurp == DECAY) + { + sl->Einc = sl->EincD; + } + else if (sl->Ecnt < ENV_END) + { + if (sl->Ecurp == SUBSTAIN) + sl->Einc = sl->EincS; + else if (sl->Ecurp == RELEASE) + sl->Einc = sl->EincR; + } + } + + if ( i2 ) + i2 = (i2 ^ 2) ^ (i2 >> 1); + } + } + + int i; + for ( i = 0; i < ym2612_channel_count; i++ ) + { + if ( !(impl->mute_mask & (1 << i)) && (i != 5 || !impl->YM2612.DAC) ) + UPDATE_CHAN [impl->YM2612.CHANNEL [i].ALGO]( &impl->g, &impl->YM2612.CHANNEL [i], out, pair_count ); + } + + impl->g.LFOcnt += impl->g.LFOinc * pair_count; +} + +void Ym2612_run( struct Ym2612_Emu* this, int pair_count, short out [] ) { impl_run( &this->impl, pair_count, out ); } diff --git a/apps/codecs/libgme/ym2612_emu.h b/apps/codecs/libgme/ym2612_emu.h new file mode 100644 index 0000000000..19f0903d61 --- /dev/null +++ b/apps/codecs/libgme/ym2612_emu.h @@ -0,0 +1,237 @@ +// YM2612 FM sound chip emulator + +// Game_Music_Emu 0.6-pre +#ifndef YM2612_EMU_H +#define YM2612_EMU_H + +#include "blargg_common.h" + +enum { ym2612_out_chan_count = 2 }; // stereo +enum { ym2612_channel_count = 6 }; +enum { ym2612_disabled_time = -1 }; + +struct slot_t +{ + const int *DT; // parametre detune + int MUL; // parametre "multiple de frequence" + int TL; // Total Level = volume lorsque l'enveloppe est au plus haut + int TLL; // Total Level ajusted + int SLL; // Sustin Level (ajusted) = volume où l'enveloppe termine sa premiere phase de regression + int KSR_S; // Key Scale Rate Shift = facteur de prise en compte du KSL dans la variations de l'enveloppe + int KSR; // Key Scale Rate = cette valeur est calculee par rapport à la frequence actuelle, elle va influer + // sur les differents parametres de l'enveloppe comme l'attaque, le decay ... comme dans la realite ! + int SEG; // Type enveloppe SSG + int env_xor; + int env_max; + + const int *AR; // Attack Rate (table pointeur) = Taux d'attaque (AR [KSR]) + const int *DR; // Decay Rate (table pointeur) = Taux pour la regression (DR [KSR]) + const int *SR; // Sustin Rate (table pointeur) = Taux pour le maintien (SR [KSR]) + const int *RR; // Release Rate (table pointeur) = Taux pour le rel'chement (RR [KSR]) + int Fcnt; // Frequency Count = compteur-frequence pour determiner l'amplitude actuelle (SIN [Finc >> 16]) + int Finc; // frequency step = pas d'incrementation du compteur-frequence + // plus le pas est grand, plus la frequence est aïgu (ou haute) + int Ecurp; // Envelope current phase = cette variable permet de savoir dans quelle phase + // de l'enveloppe on se trouve, par exemple phase d'attaque ou phase de maintenue ... + // en fonction de la valeur de cette variable, on va appeler une fonction permettant + // de mettre à jour l'enveloppe courante. + int Ecnt; // Envelope counter = le compteur-enveloppe permet de savoir où l'on se trouve dans l'enveloppe + int Einc; // Envelope step courant + int Ecmp; // Envelope counter limite pour la prochaine phase + int EincA; // Envelope step for Attack = pas d'incrementation du compteur durant la phase d'attaque + // cette valeur est egal à AR [KSR] + int EincD; // Envelope step for Decay = pas d'incrementation du compteur durant la phase de regression + // cette valeur est egal à DR [KSR] + int EincS; // Envelope step for Sustain = pas d'incrementation du compteur durant la phase de maintenue + // cette valeur est egal à SR [KSR] + int EincR; // Envelope step for Release = pas d'incrementation du compteur durant la phase de rel'chement + // cette valeur est egal à RR [KSR] + int *OUTp; // pointeur of SLOT output = pointeur permettant de connecter la sortie de ce slot à l'entree + // d'un autre ou carrement à la sortie de la voie + int INd; // input data of the slot = donnees en entree du slot + int ChgEnM; // Change envelop mask. + int AMS; // AMS depth level of this SLOT = degre de modulation de l'amplitude par le LFO + int AMSon; // AMS enable flag = drapeau d'activation de l'AMS +}; + +struct channel_ +{ + int S0_OUT [4]; // anciennes sorties slot 0 (pour le feed back) + int LEFT; // LEFT enable flag + int RIGHT; // RIGHT enable flag + int ALGO; // Algorythm = determine les connections entre les operateurs + int FB; // shift count of self feed back = degre de "Feed-Back" du SLOT 1 (il est son unique entree) + int FMS; // Frequency Modulation Sensitivity of channel = degre de modulation de la frequence sur la voie par le LFO + int AMS; // Amplitude Modulation Sensitivity of channel = degre de modulation de l'amplitude sur la voie par le LFO + int FNUM [4]; // hauteur frequence de la voie (+ 3 pour le mode special) + int FOCT [4]; // octave de la voie (+ 3 pour le mode special) + int KC [4]; // Key Code = valeur fonction de la frequence (voir KSR pour les slots, KSR = KC >> KSR_S) + struct slot_t SLOT [4]; // four slot.operators = les 4 slots de la voie + int FFlag; // Frequency step recalculation flag +}; + +struct state_t +{ + int TimerBase; // TimerBase calculation + int Status; // YM2612 Status (timer overflow) + int TimerA; // timerA limit = valeur jusqu'à laquelle le timer A doit compter + int TimerAL; + int TimerAcnt; // timerA counter = valeur courante du Timer A + int TimerB; // timerB limit = valeur jusqu'à laquelle le timer B doit compter + int TimerBL; + int TimerBcnt; // timerB counter = valeur courante du Timer B + int Mode; // Mode actuel des voie 3 et 6 (normal / special) + int DAC; // DAC enabled flag + struct channel_ CHANNEL [ym2612_channel_count]; // Les 6 voies du YM2612 + int REG [2] [0x100]; // Sauvegardes des valeurs de tout les registres, c'est facultatif + // cela nous rend le debuggage plus facile +}; + +#undef PI +#define PI 3.14159265358979323846 + +#define ATTACK 0 +#define DECAY 1 +#define SUBSTAIN 2 +#define RELEASE 3 + +// SIN_LBITS <= 16 +// LFO_HBITS <= 16 +// (SIN_LBITS + SIN_HBITS) <= 26 +// (ENV_LBITS + ENV_HBITS) <= 28 +// (LFO_LBITS + LFO_HBITS) <= 28 + +#define SIN_HBITS 12 // Sinus phase counter int part +#define SIN_LBITS (26 - SIN_HBITS) // Sinus phase counter float part (best setting) + +#if (SIN_LBITS > 16) +#define SIN_LBITS 16 // Can't be greater than 16 bits +#endif + +#define ENV_HBITS 12 // Env phase counter int part +#define ENV_LBITS (28 - ENV_HBITS) // Env phase counter float part (best setting) + +#define LFO_HBITS 10 // LFO phase counter int part +#define LFO_LBITS (28 - LFO_HBITS) // LFO phase counter float part (best setting) + +#define SIN_LENGHT (1 << SIN_HBITS) +#define ENV_LENGHT (1 << ENV_HBITS) +#define LFO_LENGHT (1 << LFO_HBITS) + +#define TL_LENGHT (ENV_LENGHT * 3) // Env + TL scaling + LFO + +#define SIN_MASK (SIN_LENGHT - 1) +#define ENV_MASK (ENV_LENGHT - 1) +#define LFO_MASK (LFO_LENGHT - 1) + +#define ENV_STEP (96.0 / ENV_LENGHT) // ENV_MAX = 96 dB + +#define ENV_ATTACK ((ENV_LENGHT * 0) << ENV_LBITS) +#define ENV_DECAY ((ENV_LENGHT * 1) << ENV_LBITS) +#define ENV_END ((ENV_LENGHT * 2) << ENV_LBITS) + +#define MAX_OUT_BITS (SIN_HBITS + SIN_LBITS + 2) // Modulation = -4 <--> +4 +#define MAX_OUT ((1 << MAX_OUT_BITS) - 1) + +#define PG_CUT_OFF ((int) (78.0 / ENV_STEP)) +//#define ENV_CUT_OFF ((int) (68.0 / ENV_STEP)) + +#define AR_RATE 399128 +#define DR_RATE 5514396 + +//#define AR_RATE 426136 +//#define DR_RATE (AR_RATE * 12) + +#define LFO_FMS_LBITS 9 // FIXED (LFO_FMS_BASE gives somethink as 1) +#define LFO_FMS_BASE ((int) (0.05946309436 * 0.0338 * (double) (1 << LFO_FMS_LBITS))) + +#define S0 0 // Stupid typo of the YM2612 +#define S1 2 +#define S2 1 +#define S3 3 + +struct tables_t +{ + short SIN_TAB [SIN_LENGHT]; // SINUS TABLE (offset into TL TABLE) + int LFOcnt; // LFO counter = compteur-frequence pour le LFO + int LFOinc; // LFO step counter = pas d'incrementation du compteur-frequence du LFO + // plus le pas est grand, plus la frequence est grande + unsigned int AR_TAB [128]; // Attack rate table + unsigned int DR_TAB [96]; // Decay rate table + unsigned int DT_TAB [8] [32]; // Detune table + unsigned int SL_TAB [16]; // Substain level table + unsigned int NULL_RATE [32]; // Table for NULL rate + int LFO_INC_TAB [8]; // LFO step table + + short ENV_TAB [2 * ENV_LENGHT + 8]; // ENV CURVE TABLE (attack & decay) + + short LFO_ENV_TAB [LFO_LENGHT]; // LFO AMS TABLE (adjusted for 11.8 dB) + short LFO_FREQ_TAB [LFO_LENGHT]; // LFO FMS TABLE + int TL_TAB [TL_LENGHT * 2]; // TOTAL LEVEL TABLE (positif and minus) + unsigned int DECAY_TO_ATTACK [ENV_LENGHT]; // Conversion from decay to attack phase + unsigned int FINC_TAB [2048]; // Frequency step table +}; + +struct Ym2612_Impl +{ + struct state_t YM2612; + int mute_mask; + struct tables_t g; +}; + +void impl_reset( struct Ym2612_Impl* impl ); + +struct Ym2612_Emu { + struct Ym2612_Impl impl; + + // Impl + int last_time; + int sample_rate; + int clock_rate; + short* out; +}; + +static inline void Ym2612_init( struct Ym2612_Emu* this_ ) +{ + this_->last_time = ym2612_disabled_time; this_->out = 0; + this_->impl.mute_mask = 0; +} + +// Sets sample rate and chip clock rate, in Hz. Returns non-zero +// if error. If clock_rate=0, uses sample_rate*144 +const char* Ym2612_set_rate( struct Ym2612_Emu* this_, double sample_rate, double clock_rate ); + +// Resets to power-up state +void Ym2612_reset( struct Ym2612_Emu* this_ ); + +// Mutes voice n if bit n (1 << n) of mask is set +void Ym2612_mute_voices( struct Ym2612_Emu* this_, int mask ); + +// Writes addr to register 0 then data to register 1 +void Ym2612_write0( struct Ym2612_Emu* this_, int addr, int data ) ICODE_ATTR; + +// Writes addr to register 2 then data to register 3 +void Ym2612_write1( struct Ym2612_Emu* this_, int addr, int data ) ICODE_ATTR; + +// Runs and adds pair_count*2 samples into current output buffer contents +void Ym2612_run( struct Ym2612_Emu* this_, int pair_count, short* out ) ICODE_ATTR; + +static inline void Ym2612_enable( struct Ym2612_Emu* this_, bool b ) { this_->last_time = b ? 0 : ym2612_disabled_time; } +static inline bool Ym2612_enabled( struct Ym2612_Emu* this_ ) { return this_->last_time != ym2612_disabled_time; } +static inline void Ym2612_begin_frame( struct Ym2612_Emu* this_, short* buf ) { this_->out = buf; this_->last_time = 0; } + +static inline int Ym2612_run_until( struct Ym2612_Emu* this_, int time ) +{ + int count = time - this_->last_time; + if ( count > 0 ) + { + if ( this_->last_time < 0 ) + return false; + this_->last_time = time; + short* p = this_->out; + this_->out += count * ym2612_out_chan_count; + Ym2612_run( this_, count, p ); + } + return true; +} +#endif diff --git a/apps/codecs/libgme/ymdeltat.c b/apps/codecs/libgme/ymdeltat.c new file mode 100644 index 0000000000..ea0be59013 --- /dev/null +++ b/apps/codecs/libgme/ymdeltat.c @@ -0,0 +1,655 @@ +/* +** +** File: ymdeltat.c +** +** YAMAHA DELTA-T adpcm sound emulation subroutine +** used by fmopl.c (Y8950) and fm.c (YM2608 and YM2610/B) +** +** Base program is YM2610 emulator by Hiromitsu Shioya. +** Written by Tatsuyuki Satoh +** Improvements by Jarek Burczynski (bujar at mame dot net) +** +** +** History: +** +** 03-08-2003 Jarek Burczynski: +** - fixed BRDY flag implementation. +** +** 24-07-2003 Jarek Burczynski, Frits Hilderink: +** - fixed delault value for control2 in YM_DELTAT_ADPCM_Reset +** +** 22-07-2003 Jarek Burczynski, Frits Hilderink: +** - fixed external memory support +** +** 15-06-2003 Jarek Burczynski: +** - implemented CPU -> AUDIO ADPCM synthesis (via writes to the ADPCM data reg $08) +** - implemented support for the Limit address register +** - supported two bits from the control register 2 ($01): RAM TYPE (x1 bit/x8 bit), ROM/RAM +** - implemented external memory access (read/write) via the ADPCM data reg reads/writes +** Thanks go to Frits Hilderink for the example code. +** +** 14-06-2003 Jarek Burczynski: +** - various fixes to enable proper support for status register flags: BSRDY, PCM BSY, ZERO +** - modified EOS handling +** +** 05-04-2003 Jarek Burczynski: +** - implemented partial support for external/processor memory on sample replay +** +** 01-12-2002 Jarek Burczynski: +** - fixed first missing sound in gigandes thanks to previous fix (interpolator) by ElSemi +** - renamed/removed some YM_DELTAT struct fields +** +** 28-12-2001 Acho A. Tang +** - added EOS status report on ADPCM playback. +** +** 05-08-2001 Jarek Burczynski: +** - now_step is initialized with 0 at the start of play. +** +** 12-06-2001 Jarek Burczynski: +** - corrected end of sample bug in YM_DELTAT_ADPCM_CALC. +** Checked on real YM2610 chip - address register is 24 bits wide. +** Thanks go to Stefan Jokisch (stefan.jokisch@gmx.de) for tracking down the problem. +** +** TO DO: +** Check size of the address register on the other chips.... +** +** Version 0.72 +** +** sound chips that have this unit: +** YM2608 OPNA +** YM2610/B OPNB +** Y8950 MSX AUDIO +** +*/ + +#include "ymdeltat.h" +#define INLINE __inline +#define logerror (void) + +#define YM_DELTAT_DELTA_MAX (24576) +#define YM_DELTAT_DELTA_MIN (127) +#define YM_DELTAT_DELTA_DEF (127) + +#define YM_DELTAT_DECODE_RANGE 32768 +#define YM_DELTAT_DECODE_MIN (-(YM_DELTAT_DECODE_RANGE)) +#define YM_DELTAT_DECODE_MAX ((YM_DELTAT_DECODE_RANGE)-1) + + +/* Forecast to next Forecast (rate = *8) */ +/* 1/8 , 3/8 , 5/8 , 7/8 , 9/8 , 11/8 , 13/8 , 15/8 */ +static const INT32 ym_deltat_decode_tableB1[16] ICONST_ATTR = { + 1, 3, 5, 7, 9, 11, 13, 15, + -1, -3, -5, -7, -9, -11, -13, -15, +}; +/* delta to next delta (rate= *64) */ +/* 0.9 , 0.9 , 0.9 , 0.9 , 1.2 , 1.6 , 2.0 , 2.4 */ +static const INT32 ym_deltat_decode_tableB2[16] ICONST_ATTR = { + 57, 57, 57, 57, 77, 102, 128, 153, + 57, 57, 57, 57, 77, 102, 128, 153 +}; + +#if 0 +void YM_DELTAT_BRDY_callback(YM_DELTAT *DELTAT) +{ + logerror("BRDY_callback reached (flag set) !\n"); + + /* set BRDY bit in status register */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); +} +#endif + +UINT8 YM_DELTAT_ADPCM_Read(YM_DELTAT *DELTAT) +{ + UINT8 v = 0; + + /* external memory read */ + if ( (DELTAT->portstate & 0xe0)==0x20 ) + { + /* two dummy reads */ + if (DELTAT->memread) + { + DELTAT->now_addr = DELTAT->start << 1; + DELTAT->memread--; + return 0; + } + + + if ( DELTAT->now_addr != (DELTAT->end<<1) ) + { + v = DELTAT->memory[DELTAT->now_addr>>1]; + + /*logerror("YM Delta-T memory read $%08x, v=$%02x\n", DELTAT->now_addr >> 1, v);*/ + + DELTAT->now_addr+=2; /* two nibbles at a time */ + + /* reset BRDY bit in status register, which means we are reading the memory now */ + if(DELTAT->status_reset_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_reset_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); + + /* setup a timer that will callback us in 10 master clock cycles for Y8950 + * in the callback set the BRDY flag to 1 , which means we have another data ready. + * For now, we don't really do this; we simply reset and set the flag in zero time, so that the IRQ will work. + */ + /* set BRDY bit in status register */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); + } + else + { + /* set EOS bit in status register */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_EOS_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_EOS_bit); + } + } + + return v; +} + + +/* 0-DRAM x1, 1-ROM, 2-DRAM x8, 3-ROM (3 is bad setting - not allowed by the manual) */ +static const UINT8 dram_rightshift[4] ICONST_ATTR ={3,0,0,0}; + +/* DELTA-T ADPCM write register */ +void YM_DELTAT_ADPCM_Write(YM_DELTAT *DELTAT,int r,int v) +{ + if(r>=0x10) return; + DELTAT->reg[r] = v; /* stock data */ + + switch( r ) + { + case 0x00: +/* +START: + Accessing *external* memory is started when START bit (D7) is set to "1", so + you must set all conditions needed for recording/playback before starting. + If you access *CPU-managed* memory, recording/playback starts after + read/write of ADPCM data register $08. + +REC: + 0 = ADPCM synthesis (playback) + 1 = ADPCM analysis (record) + +MEMDATA: + 0 = processor (*CPU-managed*) memory (means: using register $08) + 1 = external memory (using start/end/limit registers to access memory: RAM or ROM) + + +SPOFF: + controls output pin that should disable the speaker while ADPCM analysis + +RESET and REPEAT only work with external memory. + + +some examples: +value: START, REC, MEMDAT, REPEAT, SPOFF, x,x,RESET meaning: + C8 1 1 0 0 1 0 0 0 Analysis (recording) from AUDIO to CPU (to reg $08), sample rate in PRESCALER register + E8 1 1 1 0 1 0 0 0 Analysis (recording) from AUDIO to EXT.MEMORY, sample rate in PRESCALER register + 80 1 0 0 0 0 0 0 0 Synthesis (playing) from CPU (from reg $08) to AUDIO,sample rate in DELTA-N register + a0 1 0 1 0 0 0 0 0 Synthesis (playing) from EXT.MEMORY to AUDIO, sample rate in DELTA-N register + + 60 0 1 1 0 0 0 0 0 External memory write via ADPCM data register $08 + 20 0 0 1 0 0 0 0 0 External memory read via ADPCM data register $08 + +*/ + /* handle emulation mode */ + if(DELTAT->emulation_mode == YM_DELTAT_EMULATION_MODE_YM2610) + { + v |= 0x20; /* YM2610 always uses external memory and doesn't even have memory flag bit. */ + } + + DELTAT->portstate = v & (0x80|0x40|0x20|0x10|0x01); /* start, rec, memory mode, repeat flag copy, reset(bit0) */ + + if( DELTAT->portstate&0x80 )/* START,REC,MEMDATA,REPEAT,SPOFF,--,--,RESET */ + { + /* set PCM BUSY bit */ + DELTAT->PCM_BSY = 1; + + /* start ADPCM */ + DELTAT->now_step = 0; + DELTAT->acc = 0; + DELTAT->prev_acc = 0; + DELTAT->adpcml = 0; + DELTAT->adpcmd = YM_DELTAT_DELTA_DEF; + DELTAT->now_data = 0; + + } + + if( DELTAT->portstate&0x20 ) /* do we access external memory? */ + { + DELTAT->now_addr = DELTAT->start << 1; + DELTAT->memread = 2; /* two dummy reads needed before accesing external memory via register $08*/ + + /* if yes, then let's check if ADPCM memory is mapped and big enough */ + if(DELTAT->memory == 0) + { + logerror("YM Delta-T ADPCM rom not mapped\n"); + DELTAT->portstate = 0x00; + DELTAT->PCM_BSY = 0; + } + else + { + if( DELTAT->end >= DELTAT->memory_size ) /* Check End in Range */ + { + /* logerror("YM Delta-T ADPCM end out of range: $%08x\n", DELTAT->end); */ + DELTAT->end = DELTAT->memory_size - 1; + } + if( DELTAT->start >= DELTAT->memory_size ) /* Check Start in Range */ + { + /* logerror("YM Delta-T ADPCM start out of range: $%08x\n", DELTAT->start); */ + DELTAT->portstate = 0x00; + DELTAT->PCM_BSY = 0; + } + } + } + else /* we access CPU memory (ADPCM data register $08) so we only reset now_addr here */ + { + DELTAT->now_addr = 0; + } + + if( DELTAT->portstate&0x01 ) + { + DELTAT->portstate = 0x00; + + /* clear PCM BUSY bit (in status register) */ + DELTAT->PCM_BSY = 0; + + /* set BRDY flag */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); + } + break; + case 0x01: /* L,R,-,-,SAMPLE,DA/AD,RAMTYPE,ROM */ + /* handle emulation mode */ + if(DELTAT->emulation_mode == YM_DELTAT_EMULATION_MODE_YM2610) + { + v |= 0x01; /* YM2610 always uses ROM as an external memory and doesn't tave ROM/RAM memory flag bit. */ + } + + DELTAT->pan = &DELTAT->output_pointer[(v>>6)&0x03]; + if ((DELTAT->control2 & 3) != (v & 3)) + { + /*0-DRAM x1, 1-ROM, 2-DRAM x8, 3-ROM (3 is bad setting - not allowed by the manual) */ + if (DELTAT->DRAMportshift != dram_rightshift[v&3]) + { + DELTAT->DRAMportshift = dram_rightshift[v&3]; + + /* final shift value depends on chip type and memory type selected: + 8 for YM2610 (ROM only), + 5 for ROM for Y8950 and YM2608, + 5 for x8bit DRAMs for Y8950 and YM2608, + 2 for x1bit DRAMs for Y8950 and YM2608. + */ + + /* refresh addresses */ + DELTAT->start = (DELTAT->reg[0x3]*0x0100 | DELTAT->reg[0x2]) << (DELTAT->portshift - DELTAT->DRAMportshift); + DELTAT->end = (DELTAT->reg[0x5]*0x0100 | DELTAT->reg[0x4]) << (DELTAT->portshift - DELTAT->DRAMportshift); + DELTAT->end += (1 << (DELTAT->portshift-DELTAT->DRAMportshift) ) - 1; + DELTAT->limit = (DELTAT->reg[0xd]*0x0100 | DELTAT->reg[0xc]) << (DELTAT->portshift - DELTAT->DRAMportshift); + } + } + DELTAT->control2 = v; + break; + case 0x02: /* Start Address L */ + case 0x03: /* Start Address H */ + DELTAT->start = (DELTAT->reg[0x3]*0x0100 | DELTAT->reg[0x2]) << (DELTAT->portshift - DELTAT->DRAMportshift); + /*logerror("DELTAT start: 02=%2x 03=%2x addr=%8x\n",DELTAT->reg[0x2], DELTAT->reg[0x3],DELTAT->start );*/ + break; + case 0x04: /* Stop Address L */ + case 0x05: /* Stop Address H */ + DELTAT->end = (DELTAT->reg[0x5]*0x0100 | DELTAT->reg[0x4]) << (DELTAT->portshift - DELTAT->DRAMportshift); + DELTAT->end += (1 << (DELTAT->portshift-DELTAT->DRAMportshift) ) - 1; + /*logerror("DELTAT end : 04=%2x 05=%2x addr=%8x\n",DELTAT->reg[0x4], DELTAT->reg[0x5],DELTAT->end );*/ + break; + case 0x06: /* Prescale L (ADPCM and Record frq) */ + case 0x07: /* Prescale H */ + break; + case 0x08: /* ADPCM data */ + +/* +some examples: +value: START, REC, MEMDAT, REPEAT, SPOFF, x,x,RESET meaning: + C8 1 1 0 0 1 0 0 0 Analysis (recording) from AUDIO to CPU (to reg $08), sample rate in PRESCALER register + E8 1 1 1 0 1 0 0 0 Analysis (recording) from AUDIO to EXT.MEMORY, sample rate in PRESCALER register + 80 1 0 0 0 0 0 0 0 Synthesis (playing) from CPU (from reg $08) to AUDIO,sample rate in DELTA-N register + a0 1 0 1 0 0 0 0 0 Synthesis (playing) from EXT.MEMORY to AUDIO, sample rate in DELTA-N register + + 60 0 1 1 0 0 0 0 0 External memory write via ADPCM data register $08 + 20 0 0 1 0 0 0 0 0 External memory read via ADPCM data register $08 + +*/ + + /* external memory write */ + if ( (DELTAT->portstate & 0xe0)==0x60 ) + { + if (DELTAT->memread) + { + DELTAT->now_addr = DELTAT->start << 1; + DELTAT->memread = 0; + } + + /*logerror("YM Delta-T memory write $%08x, v=$%02x\n", DELTAT->now_addr >> 1, v);*/ + + if ( DELTAT->now_addr != (DELTAT->end<<1) ) + { + DELTAT->memory[DELTAT->now_addr>>1] = v; + DELTAT->now_addr+=2; /* two nibbles at a time */ + + /* reset BRDY bit in status register, which means we are processing the write */ + if(DELTAT->status_reset_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_reset_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); + + /* setup a timer that will callback us in 10 master clock cycles for Y8950 + * in the callback set the BRDY flag to 1 , which means we have written the data. + * For now, we don't really do this; we simply reset and set the flag in zero time, so that the IRQ will work. + */ + /* set BRDY bit in status register */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); + + } + else + { + /* set EOS bit in status register */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_EOS_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_EOS_bit); + } + + return; + } + + /* ADPCM synthesis from CPU */ + if ( (DELTAT->portstate & 0xe0)==0x80 ) + { + DELTAT->CPU_data = v; + + /* Reset BRDY bit in status register, which means we are full of data */ + if(DELTAT->status_reset_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_reset_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); + return; + } + + break; + case 0x09: /* DELTA-N L (ADPCM Playback Prescaler) */ + case 0x0a: /* DELTA-N H */ + DELTAT->delta = (DELTAT->reg[0xa]*0x0100 | DELTAT->reg[0x9]); + DELTAT->step = (UINT32)( (double)(DELTAT->delta /* *(1<<(YM_DELTAT_SHIFT-16)) */ ) * (DELTAT->freqbase) ); + /*logerror("DELTAT deltan:09=%2x 0a=%2x\n",DELTAT->reg[0x9], DELTAT->reg[0xa]);*/ + break; + case 0x0b: /* Output level control (volume, linear) */ + { + INT32 oldvol = DELTAT->volume; + DELTAT->volume = (v&0xff) * (DELTAT->output_range/256) / YM_DELTAT_DECODE_RANGE; +/* v * ((1<<16)>>8) >> 15; +* thus: v * (1<<8) >> 15; +* thus: output_range must be (1 << (15+8)) at least +* v * ((1<<23)>>8) >> 15; +* v * (1<<15) >> 15; +*/ + /*logerror("DELTAT vol = %2x\n",v&0xff);*/ + if( oldvol != 0 ) + { + DELTAT->adpcml = (int)((double)DELTAT->adpcml / (double)oldvol * (double)DELTAT->volume); + } + } + break; + case 0x0c: /* Limit Address L */ + case 0x0d: /* Limit Address H */ + DELTAT->limit = (DELTAT->reg[0xd]*0x0100 | DELTAT->reg[0xc]) << (DELTAT->portshift - DELTAT->DRAMportshift); + /*logerror("DELTAT limit: 0c=%2x 0d=%2x addr=%8x\n",DELTAT->reg[0xc], DELTAT->reg[0xd],DELTAT->limit );*/ + break; + } +} + +void YM_DELTAT_ADPCM_Reset(YM_DELTAT *DELTAT,int pan,int emulation_mode) +{ + DELTAT->now_addr = 0; + DELTAT->now_step = 0; + DELTAT->step = 0; + DELTAT->start = 0; + DELTAT->end = 0; + DELTAT->limit = ~0; /* this way YM2610 and Y8950 (both of which don't have limit address reg) will still work */ + DELTAT->volume = 0; + DELTAT->pan = &DELTAT->output_pointer[pan]; + DELTAT->acc = 0; + DELTAT->prev_acc = 0; + DELTAT->adpcmd = 127; + DELTAT->adpcml = 0; + DELTAT->emulation_mode = (UINT8)emulation_mode; + DELTAT->portstate = (emulation_mode == YM_DELTAT_EMULATION_MODE_YM2610) ? 0x20 : 0; + DELTAT->control2 = (emulation_mode == YM_DELTAT_EMULATION_MODE_YM2610) ? 0x01 : 0; /* default setting depends on the emulation mode. MSX demo called "facdemo_4" doesn't setup control2 register at all and still works */ + DELTAT->DRAMportshift = dram_rightshift[DELTAT->control2 & 3]; + + /* The flag mask register disables the BRDY after the reset, however + ** as soon as the mask is enabled the flag needs to be set. */ + + /* set BRDY bit in status register */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); +} + +#if 0 +void YM_DELTAT_postload(YM_DELTAT *DELTAT,UINT8 *regs) +{ + int r; + + /* to keep adpcml */ + DELTAT->volume = 0; + /* update */ + for(r=1;r<16;r++) + YM_DELTAT_ADPCM_Write(DELTAT,r,regs[r]); + DELTAT->reg[0] = regs[0]; + + /* current rom data */ + if (DELTAT->memory) + DELTAT->now_data = *(DELTAT->memory + (DELTAT->now_addr>>1) ); + +} +void YM_DELTAT_savestate(const device_config *device,YM_DELTAT *DELTAT) +{ +#ifdef __STATE_H__ + state_save_register_device_item(device, 0, DELTAT->portstate); + state_save_register_device_item(device, 0, DELTAT->now_addr); + state_save_register_device_item(device, 0, DELTAT->now_step); + state_save_register_device_item(device, 0, DELTAT->acc); + state_save_register_device_item(device, 0, DELTAT->prev_acc); + state_save_register_device_item(device, 0, DELTAT->adpcmd); + state_save_register_device_item(device, 0, DELTAT->adpcml); +#endif +} +#endif + + +#define YM_DELTAT_Limit(val,max,min) \ +{ \ + if ( val > max ) val = max; \ + else if ( val < min ) val = min; \ +} + +static INLINE void YM_DELTAT_synthesis_from_external_memory(YM_DELTAT *DELTAT) +{ + UINT32 step; + int data; + + DELTAT->now_step += DELTAT->step; + if ( DELTAT->now_step >= (1<now_step >> YM_DELTAT_SHIFT; + DELTAT->now_step &= (1<now_addr == (DELTAT->limit<<1) ) + DELTAT->now_addr = 0; + + if ( DELTAT->now_addr == (DELTAT->end<<1) ) { /* 12-06-2001 JB: corrected comparison. Was > instead of == */ + if( DELTAT->portstate&0x10 ){ + /* repeat start */ + DELTAT->now_addr = DELTAT->start<<1; + DELTAT->acc = 0; + DELTAT->adpcmd = YM_DELTAT_DELTA_DEF; + DELTAT->prev_acc = 0; + }else{ + /* set EOS bit in status register */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_EOS_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_EOS_bit); + + /* clear PCM BUSY bit (reflected in status register) */ + DELTAT->PCM_BSY = 0; + + DELTAT->portstate = 0; + DELTAT->adpcml = 0; + DELTAT->prev_acc = 0; + return; + } + } + + if( DELTAT->now_addr&1 ) data = DELTAT->now_data & 0x0f; + else + { + DELTAT->now_data = *(DELTAT->memory + (DELTAT->now_addr>>1)); + data = DELTAT->now_data >> 4; + } + + DELTAT->now_addr++; + /* 12-06-2001 JB: */ + /* YM2610 address register is 24 bits wide.*/ + /* The "+1" is there because we use 1 bit more for nibble calculations.*/ + /* WARNING: */ + /* Side effect: we should take the size of the mapped ROM into account */ + DELTAT->now_addr &= ( (1<<(24+1))-1); + + /* store accumulator value */ + DELTAT->prev_acc = DELTAT->acc; + + /* Forecast to next Forecast */ + DELTAT->acc += (ym_deltat_decode_tableB1[data] * DELTAT->adpcmd / 8); + YM_DELTAT_Limit(DELTAT->acc,YM_DELTAT_DECODE_MAX, YM_DELTAT_DECODE_MIN); + + /* delta to next delta */ + DELTAT->adpcmd = (DELTAT->adpcmd * ym_deltat_decode_tableB2[data] ) / 64; + YM_DELTAT_Limit(DELTAT->adpcmd,YM_DELTAT_DELTA_MAX, YM_DELTAT_DELTA_MIN ); + + /* ElSemi: Fix interpolator. */ + /*DELTAT->prev_acc = prev_acc + ((DELTAT->acc - prev_acc) / 2 );*/ + + }while(--step); + + } + + /* ElSemi: Fix interpolator. */ + DELTAT->adpcml = DELTAT->prev_acc * (int)((1<now_step); + DELTAT->adpcml += (DELTAT->acc * (int)DELTAT->now_step); + DELTAT->adpcml = (DELTAT->adpcml>>YM_DELTAT_SHIFT) * (int)DELTAT->volume; + + /* output for work of output channels (outd[OPNxxxx])*/ + *(DELTAT->pan) += DELTAT->adpcml; +} + + + +static INLINE void YM_DELTAT_synthesis_from_CPU_memory(YM_DELTAT *DELTAT) +{ + UINT32 step; + int data; + + DELTAT->now_step += DELTAT->step; + if ( DELTAT->now_step >= (1<now_step >> YM_DELTAT_SHIFT; + DELTAT->now_step &= (1<now_addr&1 ) + { + data = DELTAT->now_data & 0x0f; + + DELTAT->now_data = DELTAT->CPU_data; + + /* after we used CPU_data, we set BRDY bit in status register, + * which means we are ready to accept another byte of data */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); + } + else + { + data = DELTAT->now_data >> 4; + } + + DELTAT->now_addr++; + + /* store accumulator value */ + DELTAT->prev_acc = DELTAT->acc; + + /* Forecast to next Forecast */ + DELTAT->acc += (ym_deltat_decode_tableB1[data] * DELTAT->adpcmd / 8); + YM_DELTAT_Limit(DELTAT->acc,YM_DELTAT_DECODE_MAX, YM_DELTAT_DECODE_MIN); + + /* delta to next delta */ + DELTAT->adpcmd = (DELTAT->adpcmd * ym_deltat_decode_tableB2[data] ) / 64; + YM_DELTAT_Limit(DELTAT->adpcmd,YM_DELTAT_DELTA_MAX, YM_DELTAT_DELTA_MIN ); + + + }while(--step); + + } + + /* ElSemi: Fix interpolator. */ + DELTAT->adpcml = DELTAT->prev_acc * (int)((1<now_step); + DELTAT->adpcml += (DELTAT->acc * (int)DELTAT->now_step); + DELTAT->adpcml = (DELTAT->adpcml>>YM_DELTAT_SHIFT) * (int)DELTAT->volume; + + /* output for work of output channels (outd[OPNxxxx])*/ + *(DELTAT->pan) += DELTAT->adpcml; +} + + + +/* ADPCM B (Delta-T control type) */ +void YM_DELTAT_ADPCM_CALC(YM_DELTAT *DELTAT) +{ + +/* +some examples: +value: START, REC, MEMDAT, REPEAT, SPOFF, x,x,RESET meaning: + 80 1 0 0 0 0 0 0 0 Synthesis (playing) from CPU (from reg $08) to AUDIO,sample rate in DELTA-N register + a0 1 0 1 0 0 0 0 0 Synthesis (playing) from EXT.MEMORY to AUDIO, sample rate in DELTA-N register + C8 1 1 0 0 1 0 0 0 Analysis (recording) from AUDIO to CPU (to reg $08), sample rate in PRESCALER register + E8 1 1 1 0 1 0 0 0 Analysis (recording) from AUDIO to EXT.MEMORY, sample rate in PRESCALER register + + 60 0 1 1 0 0 0 0 0 External memory write via ADPCM data register $08 + 20 0 0 1 0 0 0 0 0 External memory read via ADPCM data register $08 + +*/ + + if ( (DELTAT->portstate & 0xe0)==0xa0 ) + { + YM_DELTAT_synthesis_from_external_memory(DELTAT); + return; + } + + if ( (DELTAT->portstate & 0xe0)==0x80 ) + { + /* ADPCM synthesis from CPU-managed memory (from reg $08) */ + YM_DELTAT_synthesis_from_CPU_memory(DELTAT); /* change output based on data in ADPCM data reg ($08) */ + return; + } + +//todo: ADPCM analysis +// if ( (DELTAT->portstate & 0xe0)==0xc0 ) +// if ( (DELTAT->portstate & 0xe0)==0xe0 ) + + return; +} + diff --git a/apps/codecs/libgme/ymdeltat.h b/apps/codecs/libgme/ymdeltat.h new file mode 100644 index 0000000000..01af4998a0 --- /dev/null +++ b/apps/codecs/libgme/ymdeltat.h @@ -0,0 +1,100 @@ +#pragma once + +#ifndef __YMDELTAT_H__ +#define __YMDELTAT_H__ + +#include "blargg_common.h" + +/* compiler dependence */ +#ifndef __OSDCOMM_H__ +#define __OSDCOMM_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 */ + +typedef INT32 stream_sample_t; + +#endif /* __OSDCOMM_H__ */ + +#define YM_DELTAT_SHIFT (16) + +#define YM_DELTAT_EMULATION_MODE_NORMAL 0 +#define YM_DELTAT_EMULATION_MODE_YM2610 1 + + +typedef void (*STATUS_CHANGE_HANDLER)(void *chip, UINT8 status_bits); + + +/* DELTA-T (adpcm type B) struct */ +typedef struct deltat_adpcm_state { /* AT: rearranged and tigntened structure */ + UINT8 *memory; + INT32 *output_pointer;/* pointer of output pointers */ + INT32 *pan; /* pan : &output_pointer[pan] */ + double freqbase; +#if 0 + double write_time; /* Y8950: 10 cycles of main clock; YM2608: 20 cycles of main clock */ + double read_time; /* Y8950: 8 cycles of main clock; YM2608: 18 cycles of main clock */ +#endif + UINT32 memory_size; + int output_range; + UINT32 now_addr; /* current address */ + UINT32 now_step; /* currect step */ + UINT32 step; /* step */ + UINT32 start; /* start address */ + UINT32 limit; /* limit address */ + UINT32 end; /* end address */ + UINT32 delta; /* delta scale */ + INT32 volume; /* current volume */ + INT32 acc; /* shift Measurement value*/ + INT32 adpcmd; /* next Forecast */ + INT32 adpcml; /* current value */ + INT32 prev_acc; /* leveling value */ + UINT8 now_data; /* current rom data */ + UINT8 CPU_data; /* current data from reg 08 */ + UINT8 portstate; /* port status */ + UINT8 control2; /* control reg: SAMPLE, DA/AD, RAM TYPE (x8bit / x1bit), ROM/RAM */ + UINT8 portshift; /* address bits shift-left: + ** 8 for YM2610, + ** 5 for Y8950 and YM2608 */ + + UINT8 DRAMportshift; /* address bits shift-right: + ** 0 for ROM and x8bit DRAMs, + ** 3 for x1 DRAMs */ + + UINT8 memread; /* needed for reading/writing external memory */ + + /* handlers and parameters for the status flags support */ + STATUS_CHANGE_HANDLER status_set_handler; + STATUS_CHANGE_HANDLER status_reset_handler; + + /* note that different chips have these flags on different + ** bits of the status register + */ + void * status_change_which_chip; /* this chip id */ + UINT8 status_change_EOS_bit; /* 1 on End Of Sample (record/playback/cycle time of AD/DA converting has passed)*/ + UINT8 status_change_BRDY_bit; /* 1 after recording 2 datas (2x4bits) or after reading/writing 1 data */ + UINT8 status_change_ZERO_bit; /* 1 if silence lasts for more than 290 miliseconds on ADPCM recording */ + + /* neither Y8950 nor YM2608 can generate IRQ when PCMBSY bit changes, so instead of above, + ** the statusflag gets ORed with PCM_BSY (below) (on each read of statusflag of Y8950 and YM2608) + */ + UINT8 PCM_BSY; /* 1 when ADPCM is playing; Y8950/YM2608 only */ + + UINT8 reg[16]; /* adpcm registers */ + UINT8 emulation_mode; /* which chip we're emulating */ +}YM_DELTAT; + +/*void YM_DELTAT_BRDY_callback(YM_DELTAT *DELTAT);*/ + +UINT8 YM_DELTAT_ADPCM_Read(YM_DELTAT *DELTAT) ICODE_ATTR; +void YM_DELTAT_ADPCM_Write(YM_DELTAT *DELTAT,int r,int v) ICODE_ATTR; +void YM_DELTAT_ADPCM_Reset(YM_DELTAT *DELTAT,int pan,int emulation_mode); +void YM_DELTAT_ADPCM_CALC(YM_DELTAT *DELTAT) ICODE_ATTR; + +/*void YM_DELTAT_postload(YM_DELTAT *DELTAT,UINT8 *regs); +void YM_DELTAT_savestate(const device_config *device,YM_DELTAT *DELTAT);*/ + +#endif /* __YMDELTAT_H__ */ diff --git a/apps/codecs/libgme/ymtables.h b/apps/codecs/libgme/ymtables.h new file mode 100644 index 0000000000..4e8f62a39b --- /dev/null +++ b/apps/codecs/libgme/ymtables.h @@ -0,0 +1,559 @@ +#ifndef _EMUTABLES_H_ +#define _EMUTABLES_H_ + +/* Precompiled ym2612 tables for use in Rockbox */ + +static const int tl_coeff[] ICONST_ATTR = { + 268435455, 267712100, 266990695, 266271234, 265553712, 264838123, 264124462, 263412725, 262702906, 261994999, 261289000, + 260584903, 259882704, 259182396, 258483976, 257787438, 257092777, 256399988, 255709066, 255020006, 254332802, 253647450, + 252963945, 252282282, 251602456, 250924462, 250248294, 249573949, 248901421, 248230705, 247561797, 246894691, 246229383, + 245565867, 244904140, 244244195, 243586029, 242929637, 242275013, 241622154, 240971053, 240321708, 239674112, 239028261, + 238384150, 237741775, 237101131, 236462214, 235825018, 235189539, 234555773, 233923714, 233293359, 232664702, 232037740, + 231412466, 230788878, 230166970, 229546738, 228928178, 228311284, 227696052, 227082479, 226470558, 225860287, 225251660, + 224644674, 224039323, 223435603, 222833510, 222233039, 221634187, 221036948, 220441319, 219847295, 219254871, 218664044, + 218074809, 217487162, 216901098, 216316614, 215733704, 215152366, 214572594, 213994384, 213417732, 212842635, 212269087, + 211697084, 211126623, 210557699, 209990308, 209424446, 208860109, 208297293, 207735993, 207176206, 206617927, 206061153, + 205505879, 204952102, 204399816, 203849019, 203299706, 202751873, 202205517, 201660633, 201117217, 200575266, 200034774, + 199495740, 198958158, 198422024, 197887335, 197354088, 196822277, 196291899, 195762950, 195235427, 194709325, 194184641, + 193661370, 193139510, 192619056, 192100005, 191582352, 191066094, 190551228, 190037748, 189525653, 189014937, 188505598, + 187997631, 187491033, 186985800, 186481928, 185979414, 185478255, 184978446, 184479983, 183982864, 183487085, 182992641, + 182499530, 182007748, 181517291, 181028155, 180540338, 180053835, 179568643, 179084759, 178602178, 178120898, 177640915, + 177162225, 176684825, 176208712, 175733881, 175260330, 174788055, 174317053, 173847320, 173378853, 172911648, 172445702, + 171981012, 171517574, 171055385, 170594441, 170134740, 169676277, 169219049, 168763054, 168308287, 167854746, 167402427, + 166951327, 166501443, 166052770, 165605307, 165159050, 164713995, 164270139, 163827480, 163386013, 162945736, 162506646, + 162068738, 161632011, 161196460, 160762083, 160328877, 159896838, 159465963, 159036250, 158607694, 158180293, 157754044, + 157328943, 156904988, 156482176, 156060502, 155639965, 155220562, 154802288, 154385142, 153969119, 153554218, 153140435, + 152727766, 152316210, 151905763, 151496422, 151088184, 150681046, 150275005, 149870058, 149466203, 149063435, 148661753, + 148261154, 147861634, 147463190, 147065821, 146669522, 146274291, 145880125, 145487021, 145094976, 144703988, 144314054, + 143925170, 143537334, 143150543, 142764795, 142380086, 141996414, 141613775, 141232168, 140851589, 140472035, 140093505, + 139715994, 139339501, 138964022, 138589555, 138216097, 137843646, 137472198, 137101751, 136732302, 136363849, 135996388, + 135629918, 135264436, 134899938, 134536423, 134173887, 133812328, 133451743, 133092130, 132733486, 132375808, 132019095, + 131663342, 131308548, 130954711, 130601826, 130249893, 129898908, 129548869, 129199773, 128851618, 128504401, 128158119, + 127812771, 127468353, 127124864, 126782300, 126440659, 126099939, 125760137, 125421250, 125083277, 124746214, 124410060, + 124074812, 123740467, 123407023, 123074477, 122742828, 122412072, 122082208, 121753232, 121425143, 121097939, 120771615, + 120446172, 120121605, 119797912, 119475092, 119153142, 118832060, 118511843, 118192488, 117873994, 117556359, 117239579, + 116923653, 116608578, 116294353, 115980974, 115668439, 115356747, 115045894, 114735880, 114426700, 114118354, 113810839, + 113504152, 113198292, 112893256, 112589042, 112285648, 111983071, 111681310, 111380362, 111080225, 110780896, 110482375, + 110184657, 109887742, 109591627, 109296310, 109001789, 108708061, 108415125, 108122978, 107831619, 107541044, 107251253, + 106962243, 106674011, 106386556, 106099876, 105813968, 105528830, 105244461, 104960859, 104678020, 104395944, 104114628, + 103834069, 103554267, 103275219, 102996923, 102719377, 102442578, 102166526, 101891217, 101616650, 101342823, 101069734, + 100797381, 100525762, 100254875, 99984718, 99715288, 99446585, 99178606, 98911349, 98644812, 98378993, 98113891, + 97849503, 97585828, 97322863, 97060606, 96799057, 96538212, 96278070, 96018629, 95759887, 95501842, 95244493, + 94987837, 94731873, 94476599, 94222012, 93968112, 93714895, 93462361, 93210508, 92959333, 92708835, 92459012, + 92209863, 91961384, 91713575, 91466434, 91219959, 90974149, 90729000, 90484512, 90240683, 89997511, 89754994, + 89513131, 89271920, 89031358, 88791445, 88552178, 88313556, 88075578, 87838240, 87601542, 87365481, 87130057, + 86895267, 86661110, 86427584, 86194687, 85962418, 85730775, 85499756, 85269359, 85039583, 84810427, 84581888, + 84353965, 84126656, 83899959, 83673874, 83448397, 83223528, 82999266, 82775607, 82552551, 82330096, 82108241, + 81886984, 81666322, 81446256, 81226782, 81007900, 80789608, 80571904, 80354786, 80138254, 79922305, 79706938, + 79492151, 79277943, 79064313, 78851258, 78638777, 78426868, 78215531, 78004763, 77794564, 77584930, 77375862, + 77167357, 76959413, 76752031, 76545207, 76338940, 76133229, 75928072, 75723469, 75519416, 75315914, 75112960, + 74910552, 74708690, 74507373, 74306597, 74106363, 73906668, 73707512, 73508892, 73310807, 73113256, 72916237, + 72719749, 72523791, 72328361, 72133457, 71939079, 71745225, 71551892, 71359081, 71166789, 70975016, 70783759, + 70593018, 70402791, 70213076, 70023872, 69835179, 69646994, 69459315, 69272143, 69085475, 68899310, 68713647, + 68528484, 68343820, 68159653, 67975983, 67792808, 67610127, 67427937, 67246239, 67065030, 66884310, 66704076, + 66524328, 66345065, 66166285, 65987986, 65810168, 65632829, 65455968, 65279583, 65103674, 64928239, 64753277, + 64578786, 64404765, 64231213, 64058129, 63885511, 63713359, 63541670, 63370444, 63199679, 63029375, 62859529, + 62690141, 62521210, 62352734, 62184711, 62017142, 61850024, 61683357, 61517138, 61351368, 61186044, 61021166, + 60856731, 60692741, 60529192, 60366083, 60203414, 60041184, 59879391, 59718034, 59557111, 59396622, 59236566, + 59076941, 58917746, 58758980, 58600642, 58442730, 58285245, 58128183, 57971545, 57815329, 57659533, 57504158, + 57349201, 57194662, 57040539, 56886832, 56733539, 56580659, 56428190, 56276133, 56124486, 55973247, 55822415, + 55671990, 55521971, 55372355, 55223143, 55074333, 54925924, 54777915, 54630305, 54483092, 54336276, 54189856, + 54043830, 53898198, 53752959, 53608110, 53463652, 53319583, 53175903, 53032610, 52889702, 52747180, 52605042, + 52463287, 52321914, 52180922, 52040310, 51900076, 51760221, 51620743, 51481640, 51342912, 51204558, 51066577, + 50928968, 50791729, 50654860, 50518360, 50382228, 50246463, 50111064, 49976029, 49841359, 49707051, 49573105, + 49439520, 49306295, 49173429, 49040922, 48908771, 48776976, 48645537, 48514451, 48383719, 48253339, 48123311, + 47993633, 47864304, 47735324, 47606691, 47478405, 47350465, 47222869, 47095618, 46968709, 46842142, 46715916, + 46590031, 46464484, 46339276, 46214406, 46089871, 45965673, 45841809, 45718279, 45595082, 45472216, 45349682, + 45227478, 45105603, 44984057, 44862838, 44741946, 44621380, 44501139, 44381221, 44261627, 44142355, 44023404, + 43904774, 43786464, 43668472, 43550798, 43433442, 43316402, 43199677, 43083266, 42967170, 42851386, 42735914, + 42620753, 42505903, 42391362, 42277130, 42163206, 42049588, 41936277, 41823271, 41710570, 41598172, 41486077, + 41374285, 41262793, 41151602, 41040711, 40930118, 40819823, 40709826, 40600125, 40490720, 40381609, 40272793, + 40164269, 40056039, 39948099, 39840451, 39733093, 39626024, 39519243, 39412751, 39306545, 39200625, 39094991, + 38989642, 38884576, 38779794, 38675294, 38571075, 38467138, 38363480, 38260102, 38157002, 38054180, 37951635, + 37849367, 37747374, 37645656, 37544212, 37443042, 37342144, 37241518, 37141163, 37041078, 36941264, 36841718, + 36742440, 36643430, 36544687, 36446210, 36347998, 36250051, 36152368, 36054948, 35957790, 35860895, 35764260, + 35667886, 35571772, 35475916, 35380319, 35284980, 35189897, 35095071, 35000500, 34906184, 34812122, 34718314, + 34624758, 34531454, 34438402, 34345601, 34253050, 34160748, 34068695, 33976890, 33885332, 33794021, 33702956, + 33612137, 33521562, 33431231, 33341144, 33251299, 33161697, 33072336, 32983216, 32894336, 32805695, 32717294, + 32629130, 32541204, 32453515, 32366063, 32278846, 32191864, 32105116, 32018602, 31932322, 31846273, 31760457, + 31674872, 31589518, 31504393, 31419498, 31334832, 31250394, 31166183, 31082200, 30998442, 30914911, 30831604, + 30748522, 30665664, 30583029, 30500617, 30418426, 30336458, 30254710, 30173183, 30091875, 30010786, 29929916, + 29849263, 29768829, 29688610, 29608608, 29528822, 29449250, 29369893, 29290750, 29211820, 29133103, 29054598, + 28976304, 28898222, 28820350, 28742687, 28665234, 28587990, 28510954, 28434125, 28357503, 28281088, 28204879, + 28128875, 28053076, 27977482, 27902091, 27826903, 27751917, 27677134, 27602552, 27528172, 27453991, 27380011, + 27306230, 27232648, 27159264, 27086078, 27013089, 26940296, 26867700, 26795300, 26723094, 26651083, 26579267, + 26507643, 26436213, 26364975, 26293929, 26223075, 26152412, 26081939, 26011656, 25941562, 25871657, 25801940, + 25732412, 25663071, 25593916, 25524948, 25456166, 25387569, 25319157, 25250929, 25182886, 25115025, 25047348, + 24979852, 24912539, 24845407, 24778456, 24711686, 24645095, 24578684, 24512451, 24446397, 24380522, 24314823, + 24249302, 24183957, 24118789, 24053796, 23988978, 23924335, 23859866, 23795570, 23731448, 23667499, 23603722, + 23540117, 23476683, 23413421, 23350328, 23287406, 23224653, 23162070, 23099655, 23037408, 22975329, 22913417, + 22851673, 22790094, 22728681, 22667434, 22606352, 22545435, 22484682, 22424092, 22363666, 22303402, 22243301, + 22183362, 22123584, 22063968, 22004512, 21945216, 21886080, 21827104, 21768286, 21709627, 21651126, 21592783, + 21534597, 21476567, 21418694, 21360977, 21303416, 21246009, 21188758, 21131660, 21074717, 21017926, 20961289, + 20904805, 20848473, 20792292, 20736263, 20680385, 20624657, 20569080, 20513652, 20458374, 20403245, 20348264, + 20293432, 20238747, 20184209, 20129819, 20075575, 20021477, 19967525, 19913719, 19860057, 19806540, 19753167, + 19699938, 19646853, 19593910, 19541111, 19488453, 19435937, 19383563, 19331330, 19279238, 19227286, 19175474, + 19123802, 19072269, 19020875, 18969619, 18918502, 18867522, 18816680, 18765974, 18715405, 18664973, 18614676, + 18564515, 18514489, 18464598, 18414842, 18365219, 18315730, 18266375, 18217152, 18168062, 18119105, 18070279, + 18021585, 17973022, 17924590, 17876289, 17828118, 17780076, 17732164, 17684381, 17636727, 17589201, 17541803, + 17494533, 17447391, 17400375, 17353486, 17306724, 17260087, 17213577, 17167191, 17120930, 17074795, 17028783, + 16982896, 16937132, 16891491, 16845974, 16800579, 16755306, 16710155, 16665126, 16620219, 16575432, 16530766, + 16486221, 16441795, 16397490, 16353303, 16309236, 16265287, 16221457, 16177745, 16134151, 16090674, 16047314, + 16004072, 15960945, 15917935, 15875041, 15832263, 15789599, 15747051, 15704617, 15662298, 15620093, 15578001, + 15536023, 15494158, 15452406, 15410766, 15369239, 15327823, 15286519, 15245327, 15204245, 15163274, 15122414, + 15081663, 15041023, 15000491, 14960070, 14919757, 14879552, 14839456, 14799468, 14759588, 14719815, 14680150, + 14640591, 14601139, 14561793, 14522554, 14483420, 14444391, 14405468, 14366649, 14327935, 14289326, 14250820, + 14212418, 14174120, 14135925, 14097833, 14059843, 14021956, 13984171, 13946488, 13908906, 13871426, 13834047, + 13796768, 13759590, 13722512, 13685534, 13648655, 13611876, 13575196, 13538615, 13502132, 13465748, 13429462, + 13393273, 13357183, 13321189, 13285292, 13249492, 13213789, 13178182, 13142670, 13107255, 13071934, 13036709, + 13001579, 12966544, 12931603, 12896756, 12862003, 12827344, 12792778, 12758305, 12723925, 12689638, 12655443, + 12621341, 12587330, 12553411, 12519583, 12485846, 12452201, 12418646, 12385181, 12351807, 12318522, 12285327, + 12252222, 12219206, 12186279, 12153440, 12120690, 12088029, 12055455, 12022969, 11990571, 11958260, 11926036, + 11893899, 11861848, 11829884, 11798006, 11766214, 11734507, 11702886, 11671350, 11639900, 11608533, 11577252, + 11546055, 11514941, 11483912, 11452966, 11422104, 11391325, 11360628, 11330015, 11299484, 11269035, 11238668, + 11208384, 11178180, 11148058, 11118018, 11088058, 11058179, 11028380, 10998662, 10969024, 10939466, 10909987, + 10880588, 10851268, 10822027, 10792865, 10763781, 10734776, 10705849, 10677000, 10648228, 10619535, 10590918, + 10562379, 10533916, 10505530, 10477221, 10448988, 10420831, 10392750, 10364745, 10336815, 10308960, 10281180, + 10253476, 10225846, 10198290, 10170809, 10143401, 10116068, 10088808, 10061622, 10034509, 10007468, 9980501, + 9953607, 9926785, 9900035, 9873357, 9846752, 9820217, 9793755, 9767364, 9741043, 9714794, 9688616, + 9662508, 9636470, 9610503, 9584605, 9558778, 9533019, 9507331, 9481711, 9456161, 9430679, 9405266, + 9379922, 9354646, 9329438, 9304298, 9279225, 9254221, 9229283, 9204413, 9179610, 9154874, 9130204, + 9105601, 9081064, 9056593, 9032188, 9007849, 8983576, 8959368, 8935225, 8911147, 8887134, 8863186, + 8839302, 8815483, 8791728, 8768037, 8744409, 8720846, 8697346, 8673909, 8650535, 8627225, 8603977, + 8580792, 8557669, 8534608, 8511610, 8488674, 8465799, 8442987, 8420235, 8397545, 8374916, 8352348, + 8329841, 8307395, 8285009, 8262683, 8240418, 8218212, 8196067, 8173981, 8151954, 8129987, 8108079, + 8086230, 8064440, 8042709, 8021036, 7999422, 7977866, 7956368, 7934928, 7913545, 7892221, 7870954, + 7849744, 7828591, 7807495, 7786456, 7765474, 7744548, 7723679, 7702866, 7682109, 7661408, 7640763, + 7620173, 7599639, 7579160, 7558737, 7538368, 7518055, 7497796, 7477591, 7457441, 7437346, 7417304, + 7397317, 7377383, 7357503, 7337677, 7317904, 7298185, 7278518, 7258905, 7239344, 7219836, 7200381, + 7180978, 7161627, 7142329, 7123082, 7103888, 7084745, 7065654, 7046614, 7027625, 7008688, 6989802, + 6970966, 6952181, 6933447, 6914764, 6896130, 6877547, 6859014, 6840531, 6822098, 6803715, 6785381, + 6767096, 6748861, 6730675, 6712537, 6694449, 6676410, 6658419, 6640476, 6622582, 6604736, 6586938, + 6569188, 6551486, 6533832, 6516225, 6498666, 6481154, 6463689, 6446272, 6428901, 6411577, 6394299, + 6377069, 6359884, 6342746, 6325655, 6308609, 6291609, 6274655, 6257747, 6240884, 6224066, 6207294, + 6190568, 6173886, 6157249, 6140657, 6124110, 6107607, 6091149, 6074735, 6058365, 6042040, 6025758, + 6009521, 5993327, 5977177, 5961070, 5945007, 5928987, 5913010, 5897076, 5881185, 5865337, 5849532, + 5833769, 5818049, 5802371, 5786735, 5771141, 5755590, 5740080, 5724612, 5709186, 5693802, 5678459, + 5663157, 5647896, 5632677, 5617498, 5602361, 5587264, 5572208, 5557193, 5542218, 5527283, 5512389, + 5497534, 5482720, 5467946, 5453211, 5438517, 5423861, 5409246, 5394669, 5380132, 5365635, 5351176, + 5336756, 5322375, 5308033, 5293729, 5279464, 5265237, 5251049, 5236899, 5222787, 5208713, 5194677, + 5180679, 5166719, 5152796, 5138911, 5125063, 5111252, 5097479, 5083743, 5070044, 5056382, 5042756, + 5029167, 5015615, 5002100, 4988620, 4975178, 4961771, 4948400, 4935066, 4921767, 4908505, 4895278, + 4882086, 4868931, 4855810, 4842725, 4829676, 4816661, 4803682, 4790737, 4777827, 4764953, 4752112, + 4739307, 4726536, 4713799, 4701097, 4688429, 4675795, 4663195, 4650629, 4638097, 4625599, 4613134, + 4600703, 4588306, 4575941, 4563611, 4551313, 4539049, 4526817, 4514619, 4502453, 4490320, 4478220, + 4466153, 4454118, 4442115, 4430145, 4418207, 4406301, 4394428, 4382586, 4370776, 4358998, 4347252, + 4335538, 4323855, 4312203, 4300583, 4288994, 4277437, 4265910, 4254415, 4242950, 4231517, 4220114, + 4208742, 4197401, 4186090, 4174810, 4163560, 4152340, 4141151, 4129992, 4118863, 4107764, 4096694, + 4085655, 4074645, 4063665, 4052715, 4041794, 4030903, 4020041, 4009208, 3998404, 3987630, 3976884, + 3966168, 3955480, 3944821, 3934191, 3923590, 3913017, 3902472, 3891956, 3881469, 3871009, 3860578, + 3850175, 3839800, 3829453, 3819133, 3808842, 3798578, 3788342, 3778134, 3767953, 3757799, 3747673, + 3737574, 3727503, 3717458, 3707441, 3697450, 3687487, 3677550, 3667640, 3657757, 3647900, 3638070, + 3628267, 3618490, 3608739, 3599014, 3589316, 3579644, 3569998, 3560378, 3550783, 3541215, 3531673, + 3522156, 3512665, 3503199, 3493759, 3484344, 3474955, 3465591, 3456252, 3446939, 3437650, 3428387, + 3419148, 3409935, 3400746, 3391582, 3382443, 3373328, 3364238, 3355172, 3346131, 3337114, 3328122, + 3319153, 3310209, 3301289, 3292393, 3283521, 3274673, 3265849, 3257048, 3248271, 3239518, 3230789, + 3222083, 3213400, 3204741, 3196105, 3187493, 3178903, 3170337, 3161794, 3153274, 3144777, 3136302, + 3127851, 3119422, 3111016, 3102633, 3094272, 3085934, 3077619, 3069325, 3061054, 3052806, 3044579, + 3036375, 3028193, 3020033, 3011895, 3003779, 2995684, 2987612, 2979561, 2971532, 2963525, 2955539, + 2947575, 2939632, 2931710, 2923810, 2915931, 2908074, 2900237, 2892422, 2884628, 2876855, 2869102, + 2861371, 2853660, 2845971, 2838302, 2830653, 2823025, 2815418, 2807832, 2800265, 2792719, 2785194, + 2777689, 2770203, 2762739, 2755294, 2747869, 2740464, 2733080, 2725715, 2718370, 2711045, 2703739, + 2696453, 2689187, 2681941, 2674714, 2667506, 2660318, 2653149, 2646000, 2638870, 2631759, 2624667, + 2617594, 2610540, 2603506, 2596490, 2589493, 2582515, 2575556, 2568616, 2561694, 2554791, 2547907, + 2541041, 2534194, 2527365, 2520554, 2513762, 2506988, 2500233, 2493495, 2486776, 2480075, 2473392, + 2466727, 2460080, 2453450, 2446839, 2440246, 2433670, 2427112, 2420571, 2414049, 2407544, 2401056, + 2394586, 2388133, 2381698, 2375280, 2368879, 2362496, 2356130, 2349780, 2343448, 2337134, 2330836, + 2324555, 2318291, 2312044, 2305813, 2299600, 2293403, 2287223, 2281060, 2274913, 2268783, 2262669, + 2256572, 2250491, 2244427, 2238379, 2232347, 2226331, 2220332, 2214349, 2208382, 2202431, 2196496, + 2190577, 2184674, 2178787, 2172916, 2167060, 2161221, 2155397, 2149589, 2143796, 2138019, 2132258, + 2126512, 2120782, 2115067, 2109368, 2103683, 2098015, 2092361, 2086723, 2081100, 2075492, 2069899, + 2064321, 2058758, 2053211, 2047678, 2042160, 2036657, 2031169, 2025695, 2020237, 2014793, 2009364, + 2003949, 1998549, 1993163, 1987792, 1982436, 1977094, 1971766, 1966453, 1961154, 1955869, 1950599, + 1945342, 1940100, 1934872, 1929658, 1924458, 1919272, 1914101, 1908943, 1903799, 1898668, 1893552, + 1888450, 1883361, 1878286, 1873224, 1868176, 1863142, 1858122, 1853115, 1848121, 1843141, 1838174, + 1833221, 1828281, 1823354, 1818441, 1813540, 1808654, 1803780, 1798919, 1794072, 1789237, 1784416, + 1779607, 1774812, 1770029, 1765259, 1760502, 1755758, 1751027, 1746309, 1741603, 1736910, 1732229, + 1727561, 1722906, 1718263, 1713633, 1709015, 1704410, 1699817, 1695237, 1690669, 1686113, 1681569, + 1677038, 1672519, 1668012, 1663517, 1659034, 1654564, 1650105, 1645659, 1641224, 1636801, 1632391, + 1627992, 1623605, 1619230, 1614866, 1610515, 1606175, 1601847, 1597530, 1593225, 1588932, 1584650, + 1580380, 1576122, 1571874, 1567639, 1563414, 1559201, 1555000, 1550810, 1546631, 1542463, 1538306, + 1534161, 1530027, 1525904, 1521792, 1517691, 1513602, 1509523, 1505455, 1501399, 1497353, 1493318, + 1489294, 1485281, 1481278, 1477287, 1473306, 1469336, 1465376, 1461427, 1457489, 1453562, 1449645, + 1445738, 1441843, 1437957, 1434082, 1430218, 1426364, 1422520, 1418687, 1414864, 1411051, 1407249, + 1403457, 1399675, 1395903, 1392142, 1388390, 1384649, 1380918, 1377197, 1373486, 1369784, 1366093, + 1362412, 1358741, 1355079, 1351428, 1347786, 1344154, 1340532, 1336920, 1333317, 1329724, 1326141, + 1322567, 1319004, 1315449, 1311904, 1308369, 1304844, 1301327, 1297821, 1294323, 1290836, 1287357, + 1283888, 1280429, 1276978, 1273537, 1270105, 1266683, 1263269, 1259865, 1256470, 1253084, 1249708, + 1246340, 1242982, 1239632, 1236292, 1232960, 1229638, 1226324, 1223020, 1219724, 1216437, 1213159, + 1209890, 1206630, 1203378, 1200136, 1196902, 1193676, 1190460, 1187252, 1184052, 1180862, 1177680, + 1174506, 1171341, 1168185, 1165037, 1161897, 1158767, 1155644, 1152530, 1149424, 1146327, 1143238, + 1140157, 1137085, 1134021, 1130965, 1127917, 1124878, 1121846, 1118823, 1115809, 1112802, 1109803, + 1106813, 1103830, 1100855, 1097889, 1094931, 1091980, 1089037, 1086103, 1083176, 1080257, 1077346, + 1074443, 1071548, 1068660, 1065781, 1062909, 1060044, 1057188, 1054339, 1051498, 1048664, 1045839, + 1043020, 1040210, 1037407, 1034611, 1031823, 1029043, 1026270, 1023504, 1020746, 1017996, 1015252, + 1012517, 1009788, 1007067, 1004353, 1001647, 998948, 996256, 993571, 990894, 988224, 985561, + 982905, 980256, 977615, 974980, 972353, 969733, 967120, 964514, 961915, 959323, 956737, + 954159, 951588, 949024, 946467, 943916, 941373, 938836, 936306, 933783, 931267, 928757, + 926254, 923758, 921269, 918787, 916311, 913842, 911379, 908923, 906474, 904031, 901595, + 899166, 896743, 894326, 891916, 889513, 887116, 884725, 882341, 879963, 877592, 875227, + 872869, 870517, 868171, 865831, 863498, 861171, 858851, 856536, 854228, 851926, 849631, + 847341, 845058, 842781, 840510, 838245, 835986, 833733, 831487, 829246, 827011, 824783, + 822560, 820344, 818133, 815929, 813730, 811537, 809350, 807169, 804994, 802825, 800662, + 798504, 796352, 794206, 792066, 789932, 787803, 785680, 783563, 781452, 779346, 777246, + 775151, 773062, 770979, 768902, 766830, 764763, 762703, 760647, 758598, 756553, 754515, + 752482, 750454, 748432, 746415, 744403, 742397, 740397, 738402, 736412, 734428, 732448, + 730475, 728506, 726543, 724585, 722633, 720686, 718744, 716807, 714875, 712949, 711028, + 709112, 707201, 705295, 703394, 701499, 699609, 697723, 695843, 693968, 692098, 690233, + 688373, 686518, 684668, 682823, 680983, 679148, 677318, 675493, 673673, 671857, 670047, + 668241, 666441, 664645, 662854, 661067, 659286, 657510, 655738, 653971, 652208, 650451, + 648698, 646950, 645207, 643468, 641734, 640005, 638280, 636560, 634845, 633134, 631428, + 629727, 628030, 626337, 624650, 622966, 621288, 619613, 617944, 616279, 614618, 612962, + 611310, 609663, 608020, 606381, 604747, 603118, 601492, 599872, 598255, 596643, 595035, + 593432, 591833, 590238, 588647, 587061, 585479, 583901, 582328, 580759, 579194, 577633, + 576076, 574524, 572976, 571432, 569892, 568356, 566825, 565297, 563774, 562255, 560740, + 559229, 557722, 556219, 554720, 553225, 551734, 550248, 548765, 547286, 545811, 544341, + 542874, 541411, 539952, 538497, 537046, 535599, 534155, 532716, 531280, 529849, 528421, + 526997, 525577, 524161, 522748, 521340, 519935, 518534, 517136, 515743, 514353, 512967, + 511585, 510206, 508831, 507460, 506093, 504729, 503369, 502012, 500660, 499310, 497965, + 496623, 495285, 493950, 492619, 491292, 489968, 488648, 487331, 486018, 484708, 483402, + 482099, 480800, 479504, 478212, 476924, 475638, 474357, 473078, 471804, 470532, 469264, + 468000, 466739, 465481, 464227, 462976, 461728, 460484, 459243, 458005, 456771, 455540, + 454313, 453089, 451868, 450650, 449436, 448225, 447017, 445812, 444611, 443413, 442218, + 441026, 439838, 438653, 437470, 436292, 435116, 433943, 432774, 431608, 430445, 429285, + 428128, 426974, 425824, 424676, 423532, 422391, 421252, 420117, 418985, 417856, 416730, + 415607, 414487, 413370, 412256, 411146, 410038, 408933, 407831, 406732, 405636, 404543, + 403453, 402365, 401281, 400200, 399121, 398046, 396973, 395903, 394837, 393773, 392712, + 391653, 390598, 389545, 388496, 387449, 386405, 385363, 384325, 383289, 382257, 381226, + 380199, 379175, 378153, 377134, 376118, 375104, 374093, 373085, 372080, 371077, 370077, + 369080, 368085, 367094, 366104, 365118, 364134, 363153, 362174, 361198, 360225, 359254, + 358286, 357321, 356358, 355397, 354440, 353485, 352532, 351582, 350635, 349690, 348748, + 347808, 346871, 345936, 345004, 344074, 343147, 342222, 341300, 340380, 339463, 338548, + 337636, 336726, 335819, 334914, 334011, 333111, 332214, 331318, 330426, 329535, 328647, + 327762, 326878, 325997, 325119, 324243, 323369, 322498, 321629, 320762, 319898, 319036, + 318176, 317319, 316463, 315611, 314760, 313912, 313066, 312222, 311381, 310542, 309705, + 308871, 308038, 307208, 306380, 305555, 304731, 303910, 303091, 302275, 301460, 300648, + 299838, 299030, 298224, 297420, 296619, 295819, 295022, 294227, 293434, 292644, 291855, + 291069, 290284, 289502, 288722, 287944, 287168, 286394, 285622, 284853, 284085, 283320, + 282556, 281795, 281035, 280278, 279523, 278770, 278018, 277269, 276522, 275777, 275034, + 274293, 273553, 272816, 272081, 271348, 270617, 269888, 269160, 268435, 267712, 266990, + 266271, 265553, 264838, 264124, 263412, 262702, 261994, 261289, 260584, 259882, 259182, + 258483, 257787, 257092, 256399, 255709, 255020, 254332, 253647, 252963, 252282, 251602, + 250924, 250248, 249573, 248901, 248230, 247561, 246894, 246229, 245565, 244904, 244244, + 243586, 242929, 242275, 241622, 240971, 240321, 239674, 239028, 238384, 237741, 237101, + 236462, 235825, 235189, 234555, 233923, 233293, 232664, 232037, 231412, 230788, 230166, + 229546, 228928, 228311, 227696, 227082, 226470, 225860, 225251, 224644, 224039, 223435, + 222833, 222233, 221634, 221036, 220441, 219847, 219254, 218664, 218074, 217487, 216901, + 216316, 215733, 215152, 214572, 213994, 213417, 212842, 212269, 211697, 211126, 210557, + 209990, 209424, 208860, 208297, 207735, 207176, 206617, 206061, 205505, 204952, 204399, + 203849, 203299, 202751, 202205, 201660, 201117, 200575, 200034, 199495, 198958, 198422, + 197887, 197354, 196822, 196291, 195762, 195235, 194709, 194184, 193661, 193139, 192619, + 192100, 191582, 191066, 190551, 190037, 189525, 189014, 188505, 187997, 187491, 186985, + 186481, 185979, 185478, 184978, 184479, 183982, 183487, 182992, 182499, 182007, 181517, + 181028, 180540, 180053, 179568, 179084, 178602, 178120, 177640, 177162, 176684, 176208, + 175733, 175260, 174788, 174317, 173847, 173378, 172911, 172445, 171981, 171517, 171055, + 170594, 170134, 169676, 169219, 168763, 168308, 167854, 167402, 166951, 166501, 166052, + 165605, 165159, 164713, 164270, 163827, 163386, 162945, 162506, 162068, 161632, 161196, + 160762, 160328, 159896, 159465, 159036, 158607, 158180, 157754, 157328, 156904, 156482, + 156060, 155639, 155220, 154802, 154385, 153969, 153554, 153140, 152727, 152316, 151905, + 151496, 151088, 150681, 150275, 149870, 149466, 149063, 148661, 148261, 147861, 147463, + 147065, 146669, 146274, 145880, 145487, 145094, 144703, 144314, 143925, 143537, 143150, + 142764, 142380, 141996, 141613, 141232, 140851, 140472, 140093, 139715, 139339, 138964, + 138589, 138216, 137843, 137472, 137101, 136732, 136363, 135996, 135629, 135264, 134899, + 134536, 134173, 133812, 133451, 133092, 132733, 132375, 132019, 131663, 131308, 130954, + 130601, 130249, 129898, 129548, 129199, 128851, 128504, 128158, 127812, 127468, 127124, + 126782, 126440, 126099, 125760, 125421, 125083, 124746, 124410, 124074, 123740, 123407, + 123074, 122742, 122412, 122082, 121753, 121425, 121097, 120771, 120446, 120121, 119797, + 119475, 119153, 118832, 118511, 118192, 117873, 117556, 117239, 116923, 116608, 116294, + 115980, 115668, 115356, 115045, 114735, 114426, 114118, 113810, 113504, 113198, 112893, + 112589, 112285, 111983, 111681, 111380, 111080, 110780, 110482, 110184, 109887, 109591, + 109296, 109001, 108708, 108415, 108122, 107831, 107541, 107251, 106962, 106674, 106386, + 106099, 105813, 105528, 105244, 104960, 104678, 104395, 104114, 103834, 103554, 103275, + 102996, 102719, 102442, 102166, 101891, 101616, 101342, 101069, 100797, 100525, 100254, + 99984, 99715, 99446, 99178, 98911, 98644, 98378, 98113, 97849, 97585, 97322, + 97060, 96799, 96538, 96278, 96018, 95759, 95501, 95244, 94987, 94731, 94476, + 94222, 93968, 93714, 93462, 93210, 92959, 92708, 92459, 92209, 91961, 91713, + 91466, 91219, 90974, 90729, 90484, 90240, 89997, 89754, 89513, 89271, 89031, + 88791, 88552, 88313, 88075, 87838, 87601, 87365, 87130, 86895, 86661, 86427, + 86194, 85962, 85730, 85499, 85269, 85039, 84810, 84581, 84353, 84126, 83899, + 83673, 83448, 83223, 82999, 82775, 82552, 82330, 82108, 81886, 81666, 81446, + 81226, 81007, 80789, 80571, 80354, 80138, 79922, 79706, 79492, 79277, 79064, + 78851, 78638, 78426, 78215, 78004, 77794, 77584, 77375, 77167, 76959, 76752, + 76545, 76338, 76133, 75928, 75723, 75519, 75315, 75112, 74910, 74708, 74507, + 74306, 74106, 73906, 73707, 73508, 73310, 73113, 72916, 72719, 72523, 72328, + 72133, 71939, 71745, 71551, 71359, 71166, 70975, 70783, 70593, 70402, 70213, + 70023, 69835, 69646, 69459, 69272, 69085, 68899, 68713, 68528, 68343, 68159, + 67975, 67792, 67610, 67427, 67246, 67065, 66884, 66704, 66524, 66345, 66166, + 65987, 65810, 65632, 65455, 65279, 65103, 64928, 64753, 64578, 64404, 64231, + 64058, 63885, 63713, 63541, 63370, 63199, 63029, 62859, 62690, 62521, 62352, + 62184, 62017, 61850, 61683, 61517, 61351, 61186, 61021, 60856, 60692, 60529, + 60366, 60203, 60041, 59879, 59718, 59557, 59396, 59236, 59076, 58917, 58758, + 58600, 58442, 58285, 58128, 57971, 57815, 57659, 57504, 57349, 57194, 57040, + 56886, 56733, 56580, 56428, 56276, 56124, 55973, 55822, 55671, 55521, 55372, + 55223, 55074, 54925, 54777, 54630, 54483, 54336, 54189, 54043, 53898, 53752, + 53608, 53463, 53319, 53175, 53032, 52889, 52747, 52605, 52463, 52321, 52180, + 52040, 51900, 51760, 51620, 51481, 51342, 51204, 51066, 50928, 50791, 50654, + 50518, 50382, 50246, 50111, 49976, 49841, 49707, 49573, 49439, 49306, 49173, + 49040, 48908, 48776, 48645, 48514, 48383, 48253, 48123, 47993, 47864, 47735, + 47606, 47478, 47350, 47222, 47095, 46968, 46842, 46715, 46590, 46464, 46339, + 46214, 46089, 45965, 45841, 45718, 45595, 45472, 45349, 45227, 45105, 44984, + 44862, 44741, 44621, 44501, 44381, 44261, 44142, 44023, 43904, 43786, 43668, + 43550, 43433, 43316, 43199, 43083, 42967, 42851, 42735, 42620, 42505, 42391, + 42277, 42163, 42049, 41936, 41823, 41710, 41598, 41486, 41374, 41262, 41151, + 41040, 40930, 40819, 40709, 40600, 40490, 40381, 40272, 40164, 40056, 39948, + 39840, 39733, 39626, 39519, 39412, 39306, 39200, 39094, 38989, 38884, 38779, + 38675, 38571, 38467, 38363, 38260, 38157, 38054, 37951, 37849, 37747, 37645, + 37544, 37443, 37342, 37241, 37141, 37041, 36941, 36841, 36742, 36643, 36544, + 36446, 36347, 36250, 36152, 36054, 35957, 35860, 35764, 35667, 35571, 35475, + 35380, 35284, 35189, 35095, 35000, 34906, 34812, 34718, 34624, 34531, 34438, + 34345, 34253, 34160, 34068, 33976, 33885 +}; + +static const short sindb_coeff[] ICONST_ATTR = { + 2401, 2144, 1994, 1887, 1804, 1737, 1680, 1630, 1587, 1548, 1512, 1480, 1450, + 1423, 1397, 1373, 1351, 1330, 1310, 1291, 1273, 1255, 1239, 1223, 1208, 1194, + 1180, 1166, 1153, 1141, 1128, 1117, 1105, 1094, 1084, 1073, 1063, 1053, 1043, + 1034, 1025, 1016, 1007, 999, 990, 982, 974, 967, 959, 952, 944, 937, + 930, 923, 916, 910, 903, 897, 890, 884, 878, 872, 866, 860, 855, + 849, 843, 838, 832, 827, 822, 817, 812, 807, 802, 797, 792, 787, + 783, 778, 773, 769, 764, 760, 756, 751, 747, 743, 739, 734, 730, + 726, 722, 718, 715, 711, 707, 703, 699, 696, 692, 688, 685, 681, + 678, 674, 671, 667, 664, 661, 657, 654, 651, 648, 644, 641, 638, + 635, 632, 629, 626, 623, 620, 617, 614, 611, 608, 605, 602, 599, + 597, 594, 591, 588, 586, 583, 580, 578, 575, 572, 570, 567, 565, + 562, 560, 557, 555, 552, 550, 547, 545, 542, 540, 538, 535, 533, + 531, 528, 526, 524, 522, 519, 517, 515, 513, 510, 508, 506, 504, + 502, 500, 498, 495, 493, 491, 489, 487, 485, 483, 481, 479, 477, + 475, 473, 471, 469, 467, 465, 464, 462, 460, 458, 456, 454, 452, + 450, 449, 447, 445, 443, 441, 440, 438, 436, 434, 433, 431, 429, + 427, 426, 424, 422, 421, 419, 417, 416, 414, 412, 411, 409, 408, + 406, 404, 403, 401, 400, 398, 396, 395, 393, 392, 390, 389, 387, + 386, 384, 383, 381, 380, 378, 377, 375, 374, 372, 371, 370, 368, + 367, 365, 364, 362, 361, 360, 358, 357, 355, 354, 353, 351, 350, + 349, 347, 346, 345, 343, 342, 341, 339, 338, 337, 336, 334, 333, + 332, 330, 329, 328, 327, 325, 324, 323, 322, 320, 319, 318, 317, + 316, 314, 313, 312, 311, 310, 308, 307, 306, 305, 304, 303, 301, + 300, 299, 298, 297, 296, 295, 293, 292, 291, 290, 289, 288, 287, + 286, 285, 284, 282, 281, 280, 279, 278, 277, 276, 275, 274, 273, + 272, 271, 270, 269, 268, 267, 266, 265, 264, 263, 262, 261, 260, + 259, 258, 257, 256, 255, 254, 253, 252, 251, 250, 249, 248, 247, + 246, 245, 244, 243, 242, 241, 240, 240, 239, 238, 237, 236, 235, + 234, 233, 232, 231, 230, 230, 229, 228, 227, 226, 225, 224, 223, + 222, 222, 221, 220, 219, 218, 217, 216, 216, 215, 214, 213, 212, + 211, 211, 210, 209, 208, 207, 206, 206, 205, 204, 203, 202, 202, + 201, 200, 199, 198, 198, 197, 196, 195, 195, 194, 193, 192, 191, + 191, 190, 189, 188, 188, 187, 186, 185, 185, 184, 183, 182, 182, + 181, 180, 180, 179, 178, 177, 177, 176, 175, 174, 174, 173, 172, + 172, 171, 170, 170, 169, 168, 167, 167, 166, 165, 165, 164, 163, + 163, 162, 161, 161, 160, 159, 159, 158, 157, 157, 156, 155, 155, + 154, 153, 153, 152, 151, 151, 150, 150, 149, 148, 148, 147, 146, + 146, 145, 145, 144, 143, 143, 142, 141, 141, 140, 140, 139, 138, + 138, 137, 137, 136, 135, 135, 134, 134, 133, 133, 132, 131, 131, + 130, 130, 129, 129, 128, 127, 127, 126, 126, 125, 125, 124, 123, + 123, 122, 122, 121, 121, 120, 120, 119, 119, 118, 117, 117, 116, + 116, 115, 115, 114, 114, 113, 113, 112, 112, 111, 111, 110, 110, + 109, 109, 108, 108, 107, 107, 106, 106, 105, 105, 104, 104, 103, + 103, 102, 102, 101, 101, 100, 100, 99, 99, 98, 98, 97, 97, + 96, 96, 95, 95, 94, 94, 94, 93, 93, 92, 92, 91, 91, + 90, 90, 89, 89, 89, 88, 88, 87, 87, 86, 86, 85, 85, + 85, 84, 84, 83, 83, 82, 82, 82, 81, 81, 80, 80, 79, + 79, 79, 78, 78, 77, 77, 77, 76, 76, 75, 75, 75, 74, + 74, 73, 73, 73, 72, 72, 71, 71, 71, 70, 70, 69, 69, + 69, 68, 68, 68, 67, 67, 66, 66, 66, 65, 65, 65, 64, + 64, 63, 63, 63, 62, 62, 62, 61, 61, 61, 60, 60, 59, + 59, 59, 58, 58, 58, 57, 57, 57, 56, 56, 56, 55, 55, + 55, 54, 54, 54, 53, 53, 53, 52, 52, 52, 51, 51, 51, + 50, 50, 50, 49, 49, 49, 49, 48, 48, 48, 47, 47, 47, + 46, 46, 46, 45, 45, 45, 45, 44, 44, 44, 43, 43, 43, + 43, 42, 42, 42, 41, 41, 41, 40, 40, 40, 40, 39, 39, + 39, 39, 38, 38, 38, 37, 37, 37, 37, 36, 36, 36, 36, + 35, 35, 35, 35, 34, 34, 34, 34, 33, 33, 33, 32, 32, + 32, 32, 31, 31, 31, 31, 31, 30, 30, 30, 30, 29, 29, + 29, 29, 28, 28, 28, 28, 27, 27, 27, 27, 27, 26, 26, + 26, 26, 25, 25, 25, 25, 25, 24, 24, 24, 24, 23, 23, + 23, 23, 23, 22, 22, 22, 22, 22, 21, 21, 21, 21, 21, + 20, 20, 20, 20, 20, 19, 19, 19, 19, 19, 19, 18, 18, + 18, 18, 18, 17, 17, 17, 17, 17, 17, 16, 16, 16, 16, + 16, 15, 15, 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, + 13, 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, + 11, 11, 11, 11, 11, 11, 11, 10, 10, 10, 10, 10, 10, + 10, 10, 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + + +static const short lfo_freq_coeff[] ICONST_ATTR = { + 0, 3, 6, 9, 12, 15, 18, 21, 25, 28, 31, 34, 37, + 40, 43, 46, 50, 53, 56, 59, 62, 65, 68, 71, 74, 78, + 81, 84, 87, 90, 93, 96, 99, 102, 105, 108, 111, 115, 118, + 121, 124, 127, 130, 133, 136, 139, 142, 145, 148, 151, 154, 157, + 160, 163, 166, 169, 172, 175, 178, 180, 183, 186, 189, 192, 195, + 198, 201, 204, 207, 209, 212, 215, 218, 221, 224, 226, 229, 232, + 235, 238, 240, 243, 246, 249, 251, 254, 257, 260, 262, 265, 268, + 270, 273, 276, 278, 281, 283, 286, 289, 291, 294, 296, 299, 301, + 304, 306, 309, 311, 314, 316, 319, 321, 324, 326, 328, 331, 333, + 336, 338, 340, 343, 345, 347, 350, 352, 354, 356, 359, 361, 363, + 365, 367, 370, 372, 374, 376, 378, 380, 382, 384, 386, 388, 391, + 393, 395, 396, 398, 400, 402, 404, 406, 408, 410, 412, 414, 415, + 417, 419, 421, 423, 424, 426, 428, 430, 431, 433, 435, 436, 438, + 439, 441, 443, 444, 446, 447, 449, 450, 452, 453, 455, 456, 457, + 459, 460, 461, 463, 464, 465, 467, 468, 469, 470, 472, 473, 474, + 475, 476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 488, + 488, 489, 490, 491, 492, 493, 494, 494, 495, 496, 497, 497, 498, + 499, 499, 500, 501, 501, 502, 502, 503, 504, 504, 504, 505, 505, + 506, 506, 507, 507, 507, 508, 508, 508, 509, 509, 509, 509, 510, + 510, 510, 510, 510, 510, 510, 510, 510, 510, 511, 510, 510, 510, + 510, 510, 510, 510, 510, 510, 510, 509, 509, 509, 509, 508, 508, + 508, 507, 507, 507, 506, 506, 505, 505, 504, 504, 504, 503, 502, + 502, 501, 501, 500, 499, 499, 498, 497, 497, 496, 495, 494, 494, + 493, 492, 491, 490, 489, 488, 488, 487, 486, 485, 484, 483, 482, + 481, 480, 478, 477, 476, 475, 474, 473, 472, 470, 469, 468, 467, + 465, 464, 463, 461, 460, 459, 457, 456, 455, 453, 452, 450, 449, + 447, 446, 444, 443, 441, 439, 438, 436, 435, 433, 431, 430, 428, + 426, 424, 423, 421, 419, 417, 415, 414, 412, 410, 408, 406, 404, + 402, 400, 398, 396, 395, 393, 391, 388, 386, 384, 382, 380, 378, + 376, 374, 372, 370, 367, 365, 363, 361, 359, 356, 354, 352, 350, + 347, 345, 343, 340, 338, 336, 333, 331, 328, 326, 324, 321, 319, + 316, 314, 311, 309, 306, 304, 301, 299, 296, 294, 291, 289, 286, + 283, 281, 278, 276, 273, 270, 268, 265, 262, 260, 257, 254, 251, + 249, 246, 243, 240, 238, 235, 232, 229, 226, 224, 221, 218, 215, + 212, 209, 207, 204, 201, 198, 195, 192, 189, 186, 183, 180, 178, + 175, 172, 169, 166, 163, 160, 157, 154, 151, 148, 145, 142, 139, + 136, 133, 130, 127, 124, 121, 118, 115, 111, 108, 105, 102, 99, + 96, 93, 90, 87, 84, 81, 78, 74, 71, 68, 65, 62, 59, + 56, 53, 50, 46, 43, 40, 37, 34, 31, 28, 25, 21, 18, + 15, 12, 9, 6, 3, 0, -3, -6, -9, -12, -15, -18, -21, + -25, -28, -31, -34, -37, -40, -43, -46, -50, -53, -56, -59, -62, + -65, -68, -71, -74, -78, -81, -84, -87, -90, -93, -96, -99, -102, + -105, -108, -111, -115, -118, -121, -124, -127, -130, -133, -136, -139, -142, + -145, -148, -151, -154, -157, -160, -163, -166, -169, -172, -175, -178, -180, + -183, -186, -189, -192, -195, -198, -201, -204, -207, -209, -212, -215, -218, + -221, -224, -226, -229, -232, -235, -238, -240, -243, -246, -249, -251, -254, + -257, -260, -262, -265, -268, -270, -273, -276, -278, -281, -283, -286, -289, + -291, -294, -296, -299, -301, -304, -306, -309, -311, -314, -316, -319, -321, + -324, -326, -328, -331, -333, -336, -338, -340, -343, -345, -347, -350, -352, + -354, -356, -359, -361, -363, -365, -367, -370, -372, -374, -376, -378, -380, + -382, -384, -386, -388, -391, -393, -395, -396, -398, -400, -402, -404, -406, + -408, -410, -412, -414, -415, -417, -419, -421, -423, -424, -426, -428, -430, + -431, -433, -435, -436, -438, -439, -441, -443, -444, -446, -447, -449, -450, + -452, -453, -455, -456, -457, -459, -460, -461, -463, -464, -465, -467, -468, + -469, -470, -472, -473, -474, -475, -476, -477, -478, -480, -481, -482, -483, + -484, -485, -486, -487, -488, -488, -489, -490, -491, -492, -493, -494, -494, + -495, -496, -497, -497, -498, -499, -499, -500, -501, -501, -502, -502, -503, + -504, -504, -504, -505, -505, -506, -506, -507, -507, -507, -508, -508, -508, + -509, -509, -509, -509, -510, -510, -510, -510, -510, -510, -510, -510, -510, + -510, -511, -510, -510, -510, -510, -510, -510, -510, -510, -510, -510, -509, + -509, -509, -509, -508, -508, -508, -507, -507, -507, -506, -506, -505, -505, + -504, -504, -504, -503, -502, -502, -501, -501, -500, -499, -499, -498, -497, + -497, -496, -495, -494, -494, -493, -492, -491, -490, -489, -488, -488, -487, + -486, -485, -484, -483, -482, -481, -480, -478, -477, -476, -475, -474, -473, + -472, -470, -469, -468, -467, -465, -464, -463, -461, -460, -459, -457, -456, + -455, -453, -452, -450, -449, -447, -446, -444, -443, -441, -439, -438, -436, + -435, -433, -431, -430, -428, -426, -424, -423, -421, -419, -417, -415, -414, + -412, -410, -408, -406, -404, -402, -400, -398, -396, -395, -393, -391, -388, + -386, -384, -382, -380, -378, -376, -374, -372, -370, -367, -365, -363, -361, + -359, -356, -354, -352, -350, -347, -345, -343, -340, -338, -336, -333, -331, + -328, -326, -324, -321, -319, -316, -314, -311, -309, -306, -304, -301, -299, + -296, -294, -291, -289, -286, -283, -281, -278, -276, -273, -270, -268, -265, + -262, -260, -257, -254, -251, -249, -246, -243, -240, -238, -235, -232, -229, + -226, -224, -221, -218, -215, -212, -209, -207, -204, -201, -198, -195, -192, + -189, -186, -183, -180, -178, -175, -172, -169, -166, -163, -160, -157, -154, + -151, -148, -145, -142, -139, -136, -133, -130, -127, -124, -121, -118, -115, + -111, -108, -105, -102, -99, -96, -93, -90, -87, -84, -81, -78, -74, + -71, -68, -65, -62, -59, -56, -53, -50, -46, -43, -40, -37, -34, + -31, -28, -25, -21, -18, -15, -12, -9, -6, -3 +}; + +static const short lfo_env_coeff[] ICONST_ATTR = { + 251, 253, 254, 256, 257, 259, 260, 262, 264, 265, 267, 268, 270, + 271, 273, 274, 276, 277, 279, 281, 282, 284, 285, 287, 288, 290, + 291, 293, 294, 296, 297, 299, 300, 302, 303, 305, 306, 308, 309, + 311, 312, 314, 315, 317, 318, 320, 321, 323, 324, 326, 327, 329, + 330, 332, 333, 335, 336, 337, 339, 340, 342, 343, 345, 346, 348, + 349, 350, 352, 353, 355, 356, 357, 359, 360, 362, 363, 364, 366, + 367, 369, 370, 371, 373, 374, 375, 377, 378, 379, 381, 382, 383, + 385, 386, 387, 389, 390, 391, 392, 394, 395, 396, 397, 399, 400, + 401, 402, 404, 405, 406, 407, 409, 410, 411, 412, 413, 414, 416, + 417, 418, 419, 420, 421, 423, 424, 425, 426, 427, 428, 429, 430, + 431, 432, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, + 445, 446, 447, 448, 449, 450, 451, 452, 453, 453, 454, 455, 456, + 457, 458, 459, 460, 461, 461, 462, 463, 464, 465, 466, 466, 467, + 468, 469, 469, 470, 471, 472, 473, 473, 474, 475, 475, 476, 477, + 477, 478, 479, 479, 480, 481, 481, 482, 483, 483, 484, 484, 485, + 486, 486, 487, 487, 488, 488, 489, 489, 490, 490, 491, 491, 492, + 492, 493, 493, 493, 494, 494, 495, 495, 495, 496, 496, 497, 497, + 497, 498, 498, 498, 498, 499, 499, 499, 500, 500, 500, 500, 500, + 501, 501, 501, 501, 501, 502, 502, 502, 502, 502, 502, 502, 502, + 503, 503, 503, 503, 503, 503, 503, 503, 503, 503, 503, 503, 503, + 503, 503, 503, 503, 503, 503, 502, 502, 502, 502, 502, 502, 502, + 502, 501, 501, 501, 501, 501, 500, 500, 500, 500, 500, 499, 499, + 499, 498, 498, 498, 498, 497, 497, 497, 496, 496, 495, 495, 495, + 494, 494, 493, 493, 493, 492, 492, 491, 491, 490, 490, 489, 489, + 488, 488, 487, 487, 486, 486, 485, 484, 484, 483, 483, 482, 481, + 481, 480, 479, 479, 478, 477, 477, 476, 475, 475, 474, 473, 473, + 472, 471, 470, 469, 469, 468, 467, 466, 466, 465, 464, 463, 462, + 461, 461, 460, 459, 458, 457, 456, 455, 454, 453, 453, 452, 451, + 450, 449, 448, 447, 446, 445, 444, 443, 442, 441, 440, 439, 438, + 437, 436, 435, 434, 432, 431, 430, 429, 428, 427, 426, 425, 424, + 423, 421, 420, 419, 418, 417, 416, 414, 413, 412, 411, 410, 409, + 407, 406, 405, 404, 402, 401, 400, 399, 397, 396, 395, 394, 392, + 391, 390, 389, 387, 386, 385, 383, 382, 381, 379, 378, 377, 375, + 374, 373, 371, 370, 369, 367, 366, 364, 363, 362, 360, 359, 357, + 356, 355, 353, 352, 350, 349, 348, 346, 345, 343, 342, 340, 339, + 337, 336, 335, 333, 332, 330, 329, 327, 326, 324, 323, 321, 320, + 318, 317, 315, 314, 312, 311, 309, 308, 306, 305, 303, 302, 300, + 299, 297, 296, 294, 293, 291, 290, 288, 287, 285, 284, 282, 281, + 279, 277, 276, 274, 273, 271, 270, 268, 267, 265, 264, 262, 260, + 259, 257, 256, 254, 253, 251, 250, 248, 247, 245, 244, 242, 240, + 239, 237, 236, 234, 233, 231, 230, 228, 227, 225, 223, 222, 220, + 219, 217, 216, 214, 213, 211, 210, 208, 207, 205, 204, 202, 201, + 199, 198, 196, 195, 193, 192, 190, 189, 187, 186, 184, 183, 181, + 180, 178, 177, 175, 174, 172, 171, 169, 168, 166, 165, 164, 162, + 161, 159, 158, 156, 155, 153, 152, 151, 149, 148, 146, 145, 144, + 142, 141, 139, 138, 137, 135, 134, 133, 131, 130, 129, 127, 126, + 124, 123, 122, 120, 119, 118, 117, 115, 114, 113, 111, 110, 109, + 108, 106, 105, 104, 103, 101, 100, 99, 98, 96, 95, 94, 93, + 92, 90, 89, 88, 87, 86, 84, 83, 82, 81, 80, 79, 78, + 77, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, + 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, + 50, 49, 48, 47, 46, 45, 45, 44, 43, 42, 41, 40, 39, + 39, 38, 37, 36, 35, 35, 34, 33, 32, 31, 31, 30, 29, + 29, 28, 27, 26, 26, 25, 24, 24, 23, 22, 22, 21, 20, + 20, 19, 19, 18, 17, 17, 16, 16, 15, 15, 14, 14, 13, + 13, 12, 12, 11, 11, 10, 10, 9, 9, 9, 8, 8, 7, + 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, + 3, 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, + 6, 7, 7, 7, 8, 8, 9, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, + 19, 19, 20, 20, 21, 22, 22, 23, 24, 24, 25, 26, 26, + 27, 28, 29, 29, 30, 31, 31, 32, 33, 34, 35, 35, 36, + 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 77, 78, 79, 80, 81, 82, 83, 84, 86, 87, 88, + 89, 90, 92, 93, 94, 95, 96, 98, 99, 100, 101, 103, 104, + 105, 106, 108, 109, 110, 111, 113, 114, 115, 117, 118, 119, 120, + 122, 123, 124, 126, 127, 129, 130, 131, 133, 134, 135, 137, 138, + 139, 141, 142, 144, 145, 146, 148, 149, 151, 152, 153, 155, 156, + 158, 159, 161, 162, 164, 165, 166, 168, 169, 171, 172, 174, 175, + 177, 178, 180, 181, 183, 184, 186, 187, 189, 190, 192, 193, 195, + 196, 198, 199, 201, 202, 204, 205, 207, 208, 210, 211, 213, 214, + 216, 217, 219, 220, 222, 223, 225, 227, 228, 230, 231, 233, 234, + 236, 237, 239, 240, 242, 244, 245, 247, 248, 250 +}; + +#endif diff --git a/apps/codecs/libgme/z80_cpu.c b/apps/codecs/libgme/z80_cpu.c new file mode 100644 index 0000000000..9151350067 --- /dev/null +++ b/apps/codecs/libgme/z80_cpu.c @@ -0,0 +1,85 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "z80_cpu.h" + +/* Copyright (C) 2006-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// flags, named with hex value for clarity +int const S80 = 0x80; +int const Z40 = 0x40; +int const F20 = 0x20; +int const H10 = 0x10; +int const F08 = 0x08; +int const V04 = 0x04; +int const P04 = 0x04; +int const N02 = 0x02; +int const C01 = 0x01; + +void Z80_init( struct Z80_Cpu* this ) +{ + this->cpu_state = &this->cpu_state_; + + int i; + for ( i = 0x100; --i >= 0; ) + { + int p, even = 1; + for ( p = i; p; p >>= 1 ) + even ^= p; + int n = (i & (S80 | F20 | F08)) | ((even & 1) * P04); + this->szpc [i] = n; + this->szpc [i + 0x100] = n | C01; + } + this->szpc [0x000] |= Z40; + this->szpc [0x100] |= Z40; +} + +inline void set_page( struct Z80_Cpu* this, int i, void* write, void const* read ) +{ + int offset = Z80_CPU_OFFSET( i * page_size ); + byte * write2 = STATIC_CAST(byte *,write) - offset; + byte const* read2 = STATIC_CAST(byte const*,read ) - offset; + this->cpu_state_.write [i] = write2; + this->cpu_state_.read [i] = read2; + this->cpu_state->write [i] = write2; + this->cpu_state->read [i] = read2; +} + +void Z80_reset( struct Z80_Cpu* this, void* unmapped_write, void const* unmapped_read ) +{ + check( this->cpu_state == &this->cpu_state_ ); + this->cpu_state = &this->cpu_state_; + this->cpu_state_.time = 0; + this->cpu_state_.base = 0; + this->end_time_ = 0; + + int i; + for ( i = 0; i < page_count + 1; i++ ) + set_page( this, i, unmapped_write, unmapped_read ); + + memset( &this->r, 0, sizeof this->r ); +} + +void Z80_map_mem( struct Z80_Cpu* this, addr_t start, int size, void* write, void const* read ) +{ + // address range must begin and end on page boundaries + require( start % page_size == 0 ); + require( size % page_size == 0 ); + require( start + size <= 0x10000 ); + + int offset; + for ( offset = 0; offset < size; offset += page_size ) + set_page( this, (start + offset) >> page_bits, + STATIC_CAST(char *,write) + offset, + STATIC_CAST(char const*,read ) + offset ); +} diff --git a/apps/codecs/libgme/z80_cpu.h b/apps/codecs/libgme/z80_cpu.h new file mode 100644 index 0000000000..15115b7e53 --- /dev/null +++ b/apps/codecs/libgme/z80_cpu.h @@ -0,0 +1,116 @@ +// Z80 CPU emulator + +// Game_Music_Emu 0.6-pre +#ifndef Z80_CPU_H +#define Z80_CPU_H + +#include "blargg_source.h" +#include "blargg_endian.h" + +typedef int cpu_time_t; +typedef int addr_t; + +enum { page_bits = 10 }; +enum { page_size = 1 << page_bits }; +enum { page_count = 0x10000 / page_size }; + +// Can read this far past end of memory +enum { cpu_padding = 0x100 }; + +// Can read this many bytes past end of a page +enum { page_padding = 4 }; + +#ifdef BLARGG_BIG_ENDIAN + struct regs_t { byte b,c, d,e, h,l, flags,a; }; +#else + struct regs_t { byte c,b, e,d, l,h, a,flags; }; +#endif +// BOOST_STATIC_ASSERT( sizeof (regs_t) == 8 ); + +struct pairs_t { uint16_t bc, de, hl, fa; }; + +// Registers are not updated until run() returns +struct registers_t { + uint16_t pc; + uint16_t sp; + uint16_t ix; + uint16_t iy; + union { + struct regs_t b; // b.b, b.c, b.d, b.e, b.h, b.l, b.flags, b.a + struct pairs_t w; // w.bc, w.de, w.hl. w.fa + }; + union { + struct regs_t b; + struct pairs_t w; + } alt; + byte iff1; + byte iff2; + byte r; + byte i; + byte im; +}; + +struct cpu_state_t { + byte const* read [page_count + 1]; + byte * write [page_count + 1]; + cpu_time_t base; + cpu_time_t time; +}; + +struct Z80_Cpu { + byte szpc [0x200]; + cpu_time_t end_time_; + + struct cpu_state_t* cpu_state; // points to cpu_state_ or a local copy within run() + struct cpu_state_t cpu_state_; + + struct registers_t r; +}; + +void Z80_init( struct Z80_Cpu* this ); + +// Clears registers and maps all pages to unmapped +void Z80_reset( struct Z80_Cpu* this, void* unmapped_write, void const* unmapped_read ); + +// TODO: split mapping out of CPU + +// Maps memory. Start and size must be multiple of page_size. +void Z80_map_mem( struct Z80_Cpu* this, addr_t addr, int size, void* write, void const* read ); + +// Time of beginning of next instruction +static inline cpu_time_t Z80_time( struct Z80_Cpu* this ) { return this->cpu_state->time + this->cpu_state->base; } + +// Alter current time +static inline void Z80_set_time( struct Z80_Cpu* this, cpu_time_t t ) { this->cpu_state->time = t - this->cpu_state->base; } +static inline void Z80_adjust_time( struct Z80_Cpu* this, int delta ) { this->cpu_state->time += delta; } + +#ifdef BLARGG_NONPORTABLE + #define Z80_CPU_OFFSET( addr ) (addr) +#else + #define Z80_CPU_OFFSET( addr ) ((addr) & (page_size - 1)) +#endif + +// Maps address to pointer to that byte +static inline byte* Z80_write( struct Z80_Cpu* this, addr_t addr ) +{ + return this->cpu_state->write [(unsigned) addr >> page_bits] + Z80_CPU_OFFSET( addr ); +} + +static inline byte const* Z80_read( struct Z80_Cpu* this, addr_t addr ) +{ + return this->cpu_state->read [(unsigned) addr >> page_bits] + Z80_CPU_OFFSET( addr ); +} + +static inline void Z80_map_mem_rw( struct Z80_Cpu* this, addr_t addr, int size, void* p ) +{ + Z80_map_mem( this, addr, size, p, p ); +} + +static inline void Z80_set_end_time( struct Z80_Cpu* this, cpu_time_t t ) +{ + cpu_time_t delta = this->cpu_state->base - t; + this->cpu_state->base = t; + this->cpu_state->time += delta; +} + +#endif diff --git a/apps/codecs/libgme/z80_cpu_run.h b/apps/codecs/libgme/z80_cpu_run.h new file mode 100644 index 0000000000..18195ac92b --- /dev/null +++ b/apps/codecs/libgme/z80_cpu_run.h @@ -0,0 +1,1696 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +// Last validated with zexall 2009.12.05. +// Doesn't implement the R register or immediate interrupt after EI. +// Address wrap-around isn't completely correct, but is prevented from crashing emulator. +// 16-bit memory accesses are made directly to mapped memory, instead of using macro. + +#if 0 +/* Define these macros in the source file before #including this file. +- Parameters might be expressions, so they are best evaluated only once, +though they NEVER have side-effects, so multiple evaluation is OK. +- Output parameters might be a multiple-assignment expression like "a=x", +so they must NOT be parenthesized. +- Except where noted, time() and related functions will NOT work +correctly inside a macro. TIME() is always correct, and between FLUSH_TIME() and +CACHE_TIME() the normal time changing functions can be used. +- Macros "returning" void may use a {} statement block. */ + + // 0 <= addr <= 0xFFFF + 0x100 + // Optional; default uses whatever was set with map_mem() + int READ_MEM( addr_t ); + void WRITE_MEM( addr_t, int data ); + + // 0 <= port <= 0xFFFF (apparently upper 8 bits are output by hardware) + void OUT_PORT( int port, int data ); + int IN_PORT int port ); + + // Reference to Z80_Cpu object used for emulation + #define CPU cpu + +// The following can be used within macros: + + // Current time + time_t TIME(); + + // Allows use of time functions + void FLUSH_TIME(); + + // Must be used before end of macro if FLUSH_TIME() was used earlier + void CACHE_TIME(); + +// Configuration (optional; commented behavior if defined) + + // Optimizes as if map_mem( 0, 0x10000, FLAT_MEM, FLAT_MEM ) is always in effect + #define FLAT_MEM my_mem_array + + // If RST 7 ($FF) is encountered and PC = IDLE_ADDR, stops execution + #define IDLE_ADDR 0x1234 + + // Expanded just before beginning of code, to help debugger + #define CPU_BEGIN void my_run_cpu() { + +#endif + +/* Copyright (C) 2006-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifdef CPU_BEGIN + CPU_BEGIN +#endif + +#define R cpu->r + +// flags, named with hex value for clarity +int const S80 = 0x80; +int const Z40 = 0x40; +int const F20 = 0x20; +int const H10 = 0x10; +int const F08 = 0x08; +int const V04 = 0x04; +int const P04 = 0x04; +int const N02 = 0x02; +int const C01 = 0x01; + +#define SZ28P( n ) cpu->szpc [n] +#define SZ28PC( n ) cpu->szpc [n] +#define SZ28C( n ) (cpu->szpc [n] & ~P04) +#define SZ28( n ) SZ28C( n ) + +#define SET_R( n ) (void) (R.r = n) +#define GET_R() (R.r) + +// Time +#define TIME() (s_time + s.base) +#define FLUSH_TIME() {s.time = s_time;} +#define CACHE_TIME() {s_time = s.time;} + +// Memory +#define RW_MEM( addr, rw ) RW_PAGE( addr, rw ) [RW_OFFSET( addr )] +#define READ_CODE( addr ) RW_MEM( addr, read ) + +#ifdef FLAT_MEM + #define RW_PAGE( addr, rw ) FLAT_MEM + #define RW_OFFSET( addr ) (addr) + #define INSTR( off, addr ) READ_CODE( addr ) +#else + #define RW_PAGE( addr, rw ) s.rw [(unsigned) (addr) >> page_bits] + #define RW_OFFSET( addr ) Z80_CPU_OFFSET( addr ) + #define INSTR( off, addr ) instr [off] +#endif + +#ifndef READ_MEM + #define READ_MEM( addr ) RW_MEM( addr, read ) +#endif + +#ifndef WRITE_MEM + #define WRITE_MEM( addr, data ) (RW_MEM( addr, write ) = data) +#endif + +#define READ_WORD( addr ) GET_LE16( &RW_MEM( addr, read ) ) +#define WRITE_WORD( addr, data ) SET_LE16( &RW_MEM( addr, write ), data ) + +// Truncation +#define BYTE( n ) ((uint8_t ) (n)) /* (unsigned) n & 0xFF */ +#define SBYTE( n ) ((int8_t ) (n)) /* (BYTE( n ) ^ 0x80) - 0x80 */ +#define WORD( n ) ((uint16_t) (n)) /* (unsigned) n & 0xFFFF */ + +// Misc +#define CASE5( a, b, c, d, e ) case 0x##a:case 0x##b:case 0x##c:case 0x##d:case 0x##e +#define CASE6( a, b, c, d, e, f ) CASE5( a, b, c, d, e ): case 0x##f +#define CASE7( a, b, c, d, e, f, g ) CASE6( a, b, c, d, e, f ): case 0x##g +#define CASE8( a, b, c, d, e, f, g, h ) CASE7( a, b, c, d, e, f, g ): case 0x##h + +#ifdef BLARGG_BIG_ENDIAN + #define R8( n, offset ) ((r.r8_ - offset) [n]) +#elif BLARGG_LITTLE_ENDIAN + #define R8( n, offset ) ((r.r8_ - offset) [(n) ^ 1]) +#else + #error "Byte order of CPU must be known" +#endif + +#define R16( n, shift, offset ) (r.r16_ [((unsigned) (n) >> shift) - (offset >> shift)]) + +#define EX( x, y ) \ + {\ + int temp = x;\ + x = y;\ + y = temp;\ + } + +#define EXX( name ) \ + EX( R.alt.name, r.name ) + +bool warning = false; +{ + struct cpu_state_t s; + #ifdef FLAT_MEM + s.base = cpu->cpu_state_.base; + #else + s = cpu->cpu_state_; + #endif + cpu->cpu_state = &s; + + + union r_t { + struct regs_t b; + struct pairs_t w; + byte r8_ [8]; // indexed + uint16_t r16_ [4]; + } r; + r.b = R.b; + + cpu_time_t s_time = cpu->cpu_state_.time; + int pc = R.pc; + int sp = R.sp; + int ix = R.ix; // TODO: keep in memory for direct access? + int iy = R.iy; + int flags = R.b.flags; + + //goto loop; // confuses optimizer + s_time += 7; + pc -= 2; + +call_not_taken: + s_time -= 7; +jp_not_taken: + pc += 2; +loop: + + check( (unsigned) pc < 0x10000 + 1 ); // +1 so emulator can catch wrap-around + check( (unsigned) sp < 0x10000 ); + check( (unsigned) flags < 0x100 ); + check( (unsigned) ix < 0x10000 ); + check( (unsigned) iy < 0x10000 ); + + byte const* instr = RW_PAGE( pc, read ); + + int opcode; + + if ( RW_OFFSET( ~0 ) == ~0 ) + { + opcode = instr [RW_OFFSET( pc )]; + pc++; + instr += RW_OFFSET( pc ); + } + else + { + instr += RW_OFFSET( pc ); + opcode = *instr++; + pc++; + } + + static byte const clock_table [256 * 2] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 4,10, 7, 6, 4, 4, 7, 4, 4,11, 7, 6, 4, 4, 7, 4, // 0 + 8,10, 7, 6, 4, 4, 7, 4,12,11, 7, 6, 4, 4, 7, 4, // 1 + 7,10,16, 6, 4, 4, 7, 4, 7,11,16, 6, 4, 4, 7, 4, // 2 + 7,10,13, 6,11,11,10, 4, 7,11,13, 6, 4, 4, 7, 4, // 3 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 4 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 5 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 6 + 7, 7, 7, 7, 7, 7, 4, 7, 4, 4, 4, 4, 4, 4, 7, 4, // 7 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 8 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 9 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // A + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // B + 11,10,10,10,17,11, 7,11,11,10,10, 8,17,17, 7,11, // C + 11,10,10,11,17,11, 7,11,11, 4,10,11,17, 8, 7,11, // D + 11,10,10,19,17,11, 7,11,11, 4,10, 4,17, 8, 7,11, // E + 11,10,10, 4,17,11, 7,11,11, 6,10, 4,17, 8, 7,11, // F + + // high four bits are $ED time - 8, low four bits are $DD/$FD time - 8 + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x06,0x0C,0x02,0x00,0x00,0x03,0x00,0x00,0x07,0x0C,0x02,0x00,0x00,0x03,0x00, + 0x00,0x00,0x00,0x00,0x0F,0x0F,0x0B,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, + 0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10, + 0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10, + 0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0xA0,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0xA0, + 0x4B,0x4B,0x7B,0xCB,0x0B,0x6B,0x00,0x0B,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00, + 0x80,0x80,0x80,0x80,0x00,0x00,0x0B,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0B,0x00, + 0xD0,0xD0,0xD0,0xD0,0x00,0x00,0x0B,0x00,0xD0,0xD0,0xD0,0xD0,0x00,0x00,0x0B,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x06,0x00,0x0F,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00, + }; + + if ( s_time >= 0 ) + goto out_of_time; + s_time += clock_table [opcode]; + + #ifdef Z80_CPU_LOG_H + //log_opcode( opcode, READ_CODE( pc ) ); + z80_cpu_log( "log.txt", pc - 1, opcode, READ_CODE( pc ), + READ_CODE( pc + 1 ), READ_CODE( pc + 2 ) ); + z80_log_regs( r.b.a, r.w.bc, r.w.de, r.w.hl, sp, ix, iy ); + #endif + +#define GET_ADDR() GET_LE16( &INSTR( 0, pc ) ) + + int data; + data = INSTR( 0, pc ); + + switch ( opcode ) + { +// Common + + case 0x00: // NOP + CASE7( 40, 49, 52, 5B, 64, 6D, 7F ): // LD B,B etc. + goto loop; + + case 0x08:{// EX AF,AF' + EXX( b.a ); + EX( R.alt.b.flags, flags ); + goto loop; + } + + case 0xD3: // OUT (imm),A + pc++; + OUT_PORT( (data + r.b.a * 0x100), r.b.a ); + goto loop; + + case 0x2E: // LD L,imm + pc++; + r.b.l = data; + goto loop; + + case 0x3E: // LD A,imm + pc++; + r.b.a = data; + goto loop; + + case 0x3A:{// LD A,(addr) + int addr = GET_ADDR(); + pc += 2; + r.b.a = READ_MEM( addr ); + goto loop; + } + +// Conditional + +#define ZERO (flags & Z40) +#define CARRY (flags & C01) +#define EVEN (flags & P04) +#define MINUS (flags & S80) + +// JR +// TODO: more efficient way to handle negative branch that wraps PC around +#define JR_( cond, clocks ) {\ + pc++;\ + if ( !(cond) )\ + goto loop;\ + int offset = SBYTE( data );\ + pc = WORD( pc + offset );\ + s_time += clocks;\ + goto loop;\ +} + +#define JR( cond ) JR_( cond, 5 ) + + case 0x20: JR( !ZERO ) // JR NZ,disp + case 0x28: JR( ZERO ) // JR Z,disp + case 0x30: JR( !CARRY ) // JR NC,disp + case 0x38: JR( CARRY ) // JR C,disp + case 0x18: JR_( true,0) // JR disp + + case 0x10:{// DJNZ disp + int temp = r.b.b - 1; + r.b.b = temp; + JR( temp ) + } + +// JP +#define JP( cond ) \ + if ( !(cond) )\ + goto jp_not_taken;\ + pc = GET_ADDR();\ + goto loop; + + case 0xC2: JP( !ZERO ) // JP NZ,addr + case 0xCA: JP( ZERO ) // JP Z,addr + case 0xD2: JP( !CARRY ) // JP NC,addr + case 0xDA: JP( CARRY ) // JP C,addr + case 0xE2: JP( !EVEN ) // JP PO,addr + case 0xEA: JP( EVEN ) // JP PE,addr + case 0xF2: JP( !MINUS ) // JP P,addr + case 0xFA: JP( MINUS ) // JP M,addr + + case 0xC3: // JP addr + pc = GET_ADDR(); + goto loop; + + case 0xE9: // JP HL + pc = r.w.hl; + goto loop; + +// RET +#define RET( cond ) \ + if ( cond )\ + goto ret_taken;\ + s_time -= 6;\ + goto loop; + + case 0xC0: RET( !ZERO ) // RET NZ + case 0xC8: RET( ZERO ) // RET Z + case 0xD0: RET( !CARRY ) // RET NC + case 0xD8: RET( CARRY ) // RET C + case 0xE0: RET( !EVEN ) // RET PO + case 0xE8: RET( EVEN ) // RET PE + case 0xF0: RET( !MINUS ) // RET P + case 0xF8: RET( MINUS ) // RET M + + case 0xC9: // RET + ret_taken: + pc = READ_WORD( sp ); + sp = WORD( sp + 2 ); + goto loop; + +// CALL +#define CALL( cond ) \ + if ( cond )\ + goto call_taken;\ + goto call_not_taken; + + case 0xC4: CALL( !ZERO ) // CALL NZ,addr + case 0xCC: CALL( ZERO ) // CALL Z,addr + case 0xD4: CALL( !CARRY ) // CALL NC,addr + case 0xDC: CALL( CARRY ) // CALL C,addr + case 0xE4: CALL( !EVEN ) // CALL PO,addr + case 0xEC: CALL( EVEN ) // CALL PE,addr + case 0xF4: CALL( !MINUS ) // CALL P,addr + case 0xFC: CALL( MINUS ) // CALL M,addr + + case 0xCD:{// CALL addr + call_taken: { + int addr = pc + 2; + pc = GET_ADDR(); + sp = WORD( sp - 2 ); + WRITE_WORD( sp, addr ); + goto loop; + } + } + + case 0xFF: // RST + #ifdef IDLE_ADDR + if ( pc == IDLE_ADDR + 1 ) + goto hit_idle_addr; + #else + if ( pc > 0x10000 ) + { + pc = WORD( pc - 1 ); + s_time -= 11; + goto loop; + } + #endif + CASE7( C7, CF, D7, DF, E7, EF, F7 ): + data = pc; + pc = opcode & 0x38; + #ifdef RST_BASE + pc += RST_BASE; + #endif + goto push_data; + +// PUSH/POP + case 0xF5: // PUSH AF + data = r.b.a * 0x100u + flags; + goto push_data; + + case 0xC5: // PUSH BC + case 0xD5: // PUSH DE + case 0xE5: // PUSH HL + data = R16( opcode, 4, 0xC5 ); + push_data: + sp = WORD( sp - 2 ); + WRITE_WORD( sp, data ); + goto loop; + + case 0xF1: // POP AF + flags = READ_MEM( sp ); + r.b.a = READ_MEM( (sp + 1) ); + sp = WORD( sp + 2 ); + goto loop; + + case 0xC1: // POP BC + case 0xD1: // POP DE + case 0xE1: // POP HL + R16( opcode, 4, 0xC1 ) = READ_WORD( sp ); + sp = WORD( sp + 2 ); + goto loop; + +// ADC/ADD/SBC/SUB + case 0x96: // SUB (HL) + case 0x86: // ADD (HL) + flags &= ~C01; + case 0x9E: // SBC (HL) + case 0x8E: // ADC (HL) + data = READ_MEM( r.w.hl ); + goto adc_data; + + case 0xD6: // SUB A,imm + case 0xC6: // ADD imm + flags &= ~C01; + case 0xDE: // SBC A,imm + case 0xCE: // ADC imm + pc++; + goto adc_data; + + CASE7( 90, 91, 92, 93, 94, 95, 97 ): // SUB r + CASE7( 80, 81, 82, 83, 84, 85, 87 ): // ADD r + flags &= ~C01; + CASE7( 98, 99, 9A, 9B, 9C, 9D, 9F ): // SBC r + CASE7( 88, 89, 8A, 8B, 8C, 8D, 8F ): // ADC r + data = R8( opcode & 7, 0 ); + adc_data: { + int result = data + (flags & C01); + data ^= r.b.a; + flags = opcode >> 3 & N02; // bit 4 is set in subtract opcodes + if ( flags ) + result = -result; + result += r.b.a; + data ^= result; + flags +=(data & H10) + + ((data + 0x80) >> 6 & V04) + + SZ28C( result & 0x1FF ); + r.b.a = result; + goto loop; + } + +// CP + case 0xBE: // CP (HL) + data = READ_MEM( r.w.hl ); + goto cp_data; + + case 0xFE: // CP imm + pc++; + goto cp_data; + + CASE7( B8, B9, BA, BB, BC, BD, BF ): // CP r + data = R8( opcode, 0xB8 ); + cp_data: { + int result = r.b.a - data; + flags = N02 + (data & (F20 | F08)) + (result >> 8 & C01); + data ^= r.b.a; + flags +=(((result ^ r.b.a) & data) >> 5 & V04) + + (((data & H10) ^ result) & (S80 | H10)); + if ( BYTE( result ) ) + goto loop; + flags += Z40; + goto loop; + } + +// ADD HL,r.w + + case 0x39: // ADD HL,SP + data = sp; + goto add_hl_data; + + case 0x09: // ADD HL,BC + case 0x19: // ADD HL,DE + case 0x29: // ADD HL,HL + data = R16( opcode, 4, 0x09 ); + add_hl_data: { + int sum = r.w.hl + data; + data ^= r.w.hl; + r.w.hl = sum; + flags = (flags & (S80 | Z40 | V04)) + + (sum >> 16) + + (sum >> 8 & (F20 | F08)) + + ((data ^ sum) >> 8 & H10); + goto loop; + } + + case 0x27:{// DAA + int a = r.b.a; + if ( a > 0x99 ) + flags |= C01; + + int adjust = 0x60 * (flags & C01); + + if ( flags & H10 || (a & 0x0F) > 9 ) + adjust += 0x06; + + if ( flags & N02 ) + adjust = -adjust; + a += adjust; + + flags = (flags & (C01 | N02)) + + ((r.b.a ^ a) & H10) + + SZ28P( BYTE( a ) ); + r.b.a = a; + goto loop; + } + +// INC/DEC + case 0x34: // INC (HL) + data = READ_MEM( r.w.hl ) + 1; + WRITE_MEM( r.w.hl, data ); + goto inc_set_flags; + + CASE7( 04, 0C, 14, 1C, 24, 2C, 3C ): // INC r + data = ++R8( opcode >> 3, 0 ); + inc_set_flags: + flags = (flags & C01) + + (((data & 0x0F) - 1) & H10) + + SZ28( BYTE( data ) ); + if ( data != 0x80 ) + goto loop; + flags += V04; + goto loop; + + case 0x35: // DEC (HL) + data = READ_MEM( r.w.hl ) - 1; + WRITE_MEM( r.w.hl, data ); + goto dec_set_flags; + + CASE7( 05, 0D, 15, 1D, 25, 2D, 3D ): // DEC r + data = --R8( opcode >> 3, 0 ); + dec_set_flags: + flags = (flags & C01) + N02 + + (((data & 0x0F) + 1) & H10) + + SZ28( BYTE( data ) ); + if ( data != 0x7F ) + goto loop; + flags += V04; + goto loop; + + case 0x03: // INC BC + case 0x13: // INC DE + case 0x23: // INC HL + R16( opcode, 4, 0x03 )++; + goto loop; + + case 0x33: // INC SP + sp = WORD( sp + 1 ); + goto loop; + + case 0x0B: // DEC BC + case 0x1B: // DEC DE + case 0x2B: // DEC HL + R16( opcode, 4, 0x0B )--; + goto loop; + + case 0x3B: // DEC SP + sp = WORD( sp - 1 ); + goto loop; + +// AND + case 0xA6: // AND (HL) + data = READ_MEM( r.w.hl ); + goto and_data; + + case 0xE6: // AND imm + pc++; + goto and_data; + + CASE7( A0, A1, A2, A3, A4, A5, A7 ): // AND r + data = R8( opcode, 0xA0 ); + and_data: + r.b.a &= data; + flags = SZ28P( r.b.a ) + H10; + goto loop; + +// OR + case 0xB6: // OR (HL) + data = READ_MEM( r.w.hl ); + goto or_data; + + case 0xF6: // OR imm + pc++; + goto or_data; + + CASE7( B0, B1, B2, B3, B4, B5, B7 ): // OR r + data = R8( opcode, 0xB0 ); + or_data: + r.b.a |= data; + flags = SZ28P( r.b.a ); + goto loop; + +// XOR + case 0xAE: // XOR (HL) + data = READ_MEM( r.w.hl ); + goto xor_data; + + case 0xEE: // XOR imm + pc++; + goto xor_data; + + CASE7( A8, A9, AA, AB, AC, AD, AF ): // XOR r + data = R8( opcode, 0xA8 ); + xor_data: + r.b.a ^= data; + flags = SZ28P( r.b.a ); + goto loop; + +// LD + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // LD (HL),r + WRITE_MEM( r.w.hl, R8( opcode, 0x70 ) ); + goto loop; + + CASE6( 41, 42, 43, 44, 45, 47 ): // LD B,r + CASE6( 48, 4A, 4B, 4C, 4D, 4F ): // LD C,r + CASE6( 50, 51, 53, 54, 55, 57 ): // LD D,r + CASE6( 58, 59, 5A, 5C, 5D, 5F ): // LD E,r + CASE6( 60, 61, 62, 63, 65, 67 ): // LD H,r + CASE6( 68, 69, 6A, 6B, 6C, 6F ): // LD L,r + CASE6( 78, 79, 7A, 7B, 7C, 7D ): // LD A,r + R8( opcode >> 3 & 7, 0 ) = R8( opcode & 7, 0 ); + goto loop; + + CASE5( 06, 0E, 16, 1E, 26 ): // LD r,imm + R8( opcode >> 3, 0 ) = data; + pc++; + goto loop; + + case 0x36: // LD (HL),imm + pc++; + WRITE_MEM( r.w.hl, data ); + goto loop; + + CASE7( 46, 4E, 56, 5E, 66, 6E, 7E ): // LD r,(HL) + R8( opcode >> 3, 8 ) = READ_MEM( r.w.hl ); + goto loop; + + case 0x01: // LD r.w,imm + case 0x11: + case 0x21: + R16( opcode, 4, 0x01 ) = GET_ADDR(); + pc += 2; + goto loop; + + case 0x31: // LD sp,imm + sp = GET_ADDR(); + pc += 2; + goto loop; + + case 0x2A:{// LD HL,(addr) + int addr = GET_ADDR(); + pc += 2; + r.w.hl = READ_WORD( addr ); + goto loop; + } + + case 0x32:{// LD (addr),A + int addr = GET_ADDR(); + pc += 2; + WRITE_MEM( addr, r.b.a ); + goto loop; + } + + case 0x22:{// LD (addr),HL + int addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, r.w.hl ); + goto loop; + } + + case 0x02: // LD (BC),A + case 0x12: // LD (DE),A + WRITE_MEM( R16( opcode, 4, 0x02 ), r.b.a ); + goto loop; + + case 0x0A: // LD A,(BC) + case 0x1A: // LD A,(DE) + r.b.a = READ_MEM( R16( opcode, 4, 0x0A ) ); + goto loop; + + case 0xF9: // LD SP,HL + sp = r.w.hl; + goto loop; + +// Rotate + + case 0x07:{// RLCA + int temp = r.b.a; + temp = (temp << 1) + (temp >> 7); + flags = (flags & (S80 | Z40 | P04)) + + (temp & (F20 | F08 | C01)); + r.b.a = temp; + goto loop; + } + + case 0x0F:{// RRCA + int temp = r.b.a; + flags = (flags & (S80 | Z40 | P04)) + + (temp & C01); + temp = (temp << 7) + (temp >> 1); + flags += temp & (F20 | F08); + r.b.a = temp; + goto loop; + } + + case 0x17:{// RLA + int temp = (r.b.a << 1) + (flags & C01); + flags = (flags & (S80 | Z40 | P04)) + + (temp & (F20 | F08)) + + (temp >> 8); + r.b.a = temp; + goto loop; + } + + case 0x1F:{// RRA + int temp = (flags << 7) + (r.b.a >> 1); + flags = (flags & (S80 | Z40 | P04)) + + (temp & (F20 | F08)) + + (r.b.a & C01); + r.b.a = temp; + goto loop; + } + +// Misc + case 0x2F:{// CPL + int temp = ~r.b.a; + flags = (flags & (S80 | Z40 | P04 | C01)) + + (temp & (F20 | F08)) + + (H10 | N02); + r.b.a = temp; + goto loop; + } + + case 0x3F:{// CCF + flags = ((flags & (S80 | Z40 | P04 | C01)) ^ C01) + + (flags << 4 & H10) + + (r.b.a & (F20 | F08)); + goto loop; + } + + case 0x37: // SCF + flags = ((flags & (S80 | Z40 | P04)) | C01) + + (r.b.a & (F20 | F08)); + goto loop; + + case 0xDB: // IN A,(imm) + pc++; + r.b.a = IN_PORT( (data + r.b.a * 0x100) ); + goto loop; + + case 0xE3:{// EX (SP),HL + int temp = READ_WORD( sp ); + WRITE_WORD( sp, r.w.hl ); + r.w.hl = temp; + goto loop; + } + + case 0xEB: // EX DE,HL + EX( r.w.hl, r.w.de ); + goto loop; + + case 0xD9: // EXX DE,HL + EXX( w.bc ); + EXX( w.de ); + EXX( w.hl ); + goto loop; + + case 0xF3: // DI + R.iff1 = 0; + R.iff2 = 0; + goto loop; + + case 0xFB: // EI + R.iff1 = 1; + R.iff2 = 1; + // TODO: delayed effect + goto loop; + + case 0x76: // HALT + goto halt; + +//////////////////////////////////////// CB prefix + { + case 0xCB: + pc++; + switch ( data ) + { + + // Rotate left + + #define RLC( read, write ) {\ + int result = read;\ + result = BYTE( result << 1 ) + (result >> 7);\ + flags = SZ28P( result ) + (result & C01);\ + write;\ + goto loop;\ + } + + case 0x06: // RLC (HL) + s_time += 7; + data = r.w.hl; + rlc_data_addr: + RLC( READ_MEM( data ), WRITE_MEM( data, result ) ) + + CASE7( 00, 01, 02, 03, 04, 05, 07 ):{// RLC r + byte* reg = &R8( data, 0 ); + RLC( *reg, *reg = result ) + } + + #define RL( read, write ) {\ + int result = (read << 1) + (flags & C01);\ + flags = SZ28PC( result );\ + write;\ + goto loop;\ + } + + case 0x16: // RL (HL) + s_time += 7; + data = r.w.hl; + rl_data_addr: + RL( READ_MEM( data ), WRITE_MEM( data, result ) ) + + CASE7( 10, 11, 12, 13, 14, 15, 17 ):{// RL r + byte* reg = &R8( data, 0x10 ); + RL( *reg, *reg = result ) + } + + #define SLA( read, low_bit, write ) {\ + int result = (read << 1) + low_bit;\ + flags = SZ28PC( result );\ + write;\ + goto loop;\ + } + + case 0x26: // SLA (HL) + s_time += 7; + data = r.w.hl; + sla_data_addr: + SLA( READ_MEM( data ), 0, WRITE_MEM( data, result ) ) + + CASE7( 20, 21, 22, 23, 24, 25, 27 ):{// SLA r + byte* reg = &R8( data, 0x20 ); + SLA( *reg, 0, *reg = result ) + } + + case 0x36: // SLL (HL) + s_time += 7; + data = r.w.hl; + sll_data_addr: + SLA( READ_MEM( data ), 1, WRITE_MEM( data, result ) ) + + CASE7( 30, 31, 32, 33, 34, 35, 37 ):{// SLL r + byte* reg = &R8( data, 0x30 ); + SLA( *reg, 1, *reg = result ) + } + + // Rotate right + + #define RRC( read, write ) {\ + int result = read;\ + flags = result & C01;\ + result = BYTE( result << 7 ) + (result >> 1);\ + flags += SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x0E: // RRC (HL) + s_time += 7; + data = r.w.hl; + rrc_data_addr: + RRC( READ_MEM( data ), WRITE_MEM( data, result ) ) + + CASE7( 08, 09, 0A, 0B, 0C, 0D, 0F ):{// RRC r + byte* reg = &R8( data, 0x08 ); + RRC( *reg, *reg = result ) + } + + #define RR( read, write ) {\ + int result = read;\ + int temp = result & C01;\ + result = BYTE( flags << 7 ) + (result >> 1);\ + flags = SZ28P( result ) + temp;\ + write;\ + goto loop;\ + } + + case 0x1E: // RR (HL) + s_time += 7; + data = r.w.hl; + rr_data_addr: + RR( READ_MEM( data ), WRITE_MEM( data, result ) ) + + CASE7( 18, 19, 1A, 1B, 1C, 1D, 1F ):{// RR r + byte* reg = &R8( data, 0x18 ); + RR( *reg, *reg = result ) + } + + #define SRA( read, write ) {\ + int result = read;\ + flags = result & C01;\ + result = (result & 0x80) + (result >> 1);\ + flags += SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x2E: // SRA (HL) + data = r.w.hl; + s_time += 7; + sra_data_addr: + SRA( READ_MEM( data ), WRITE_MEM( data, result ) ) + + CASE7( 28, 29, 2A, 2B, 2C, 2D, 2F ):{// SRA r + byte* reg = &R8( data, 0x28 ); + SRA( *reg, *reg = result ) + } + + #define SRL( read, write ) {\ + int result = read;\ + flags = result & C01;\ + result >>= 1;\ + flags += SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x3E: // SRL (HL) + s_time += 7; + data = r.w.hl; + srl_data_addr: + SRL( READ_MEM( data ), WRITE_MEM( data, result ) ) + + CASE7( 38, 39, 3A, 3B, 3C, 3D, 3F ):{// SRL r + byte* reg = &R8( data, 0x38 ); + SRL( *reg, *reg = result ) + } + + // BIT + { + int temp; + CASE8( 46, 4E, 56, 5E, 66, 6E, 76, 7E ): // BIT b,(HL) + s_time += 4; + temp = READ_MEM( r.w.hl ); + flags &= C01; + goto bit_temp; + CASE7( 40, 41, 42, 43, 44, 45, 47 ): // BIT 0,r + CASE7( 48, 49, 4A, 4B, 4C, 4D, 4F ): // BIT 1,r + CASE7( 50, 51, 52, 53, 54, 55, 57 ): // BIT 2,r + CASE7( 58, 59, 5A, 5B, 5C, 5D, 5F ): // BIT 3,r + CASE7( 60, 61, 62, 63, 64, 65, 67 ): // BIT 4,r + CASE7( 68, 69, 6A, 6B, 6C, 6D, 6F ): // BIT 5,r + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // BIT 6,r + CASE7( 78, 79, 7A, 7B, 7C, 7D, 7F ): // BIT 7,r + temp = R8( data & 7, 0 ); + flags = (flags & C01) + (temp & (F20 | F08)); + bit_temp: + temp = temp & (1 << (data >> 3 & 7)); + flags += (temp & S80) + H10; + flags += (unsigned) --temp >> 8 & (Z40 | P04); + goto loop; + } + + // SET/RES + CASE8( 86, 8E, 96, 9E, A6, AE, B6, BE ): // RES b,(HL) + CASE8( C6, CE, D6, DE, E6, EE, F6, FE ):{// SET b,(HL) + s_time += 7; + int temp = READ_MEM( r.w.hl ); + int bit = 1 << (data >> 3 & 7); + temp |= bit; // SET + if ( !(data & 0x40) ) + temp ^= bit; // RES + WRITE_MEM( r.w.hl, temp ); + goto loop; + } + + CASE7( C0, C1, C2, C3, C4, C5, C7 ): // SET 0,r + CASE7( C8, C9, CA, CB, CC, CD, CF ): // SET 1,r + CASE7( D0, D1, D2, D3, D4, D5, D7 ): // SET 2,r + CASE7( D8, D9, DA, DB, DC, DD, DF ): // SET 3,r + CASE7( E0, E1, E2, E3, E4, E5, E7 ): // SET 4,r + CASE7( E8, E9, EA, EB, EC, ED, EF ): // SET 5,r + CASE7( F0, F1, F2, F3, F4, F5, F7 ): // SET 6,r + CASE7( F8, F9, FA, FB, FC, FD, FF ): // SET 7,r + R8( data & 7, 0 ) |= 1 << (data >> 3 & 7); + goto loop; + + CASE7( 80, 81, 82, 83, 84, 85, 87 ): // RES 0,r + CASE7( 88, 89, 8A, 8B, 8C, 8D, 8F ): // RES 1,r + CASE7( 90, 91, 92, 93, 94, 95, 97 ): // RES 2,r + CASE7( 98, 99, 9A, 9B, 9C, 9D, 9F ): // RES 3,r + CASE7( A0, A1, A2, A3, A4, A5, A7 ): // RES 4,r + CASE7( A8, A9, AA, AB, AC, AD, AF ): // RES 5,r + CASE7( B0, B1, B2, B3, B4, B5, B7 ): // RES 6,r + CASE7( B8, B9, BA, BB, BC, BD, BF ): // RES 7,r + R8( data & 7, 0 ) &= ~(1 << (data >> 3 & 7)); + goto loop; + } + assert( false ); + } + +#undef GET_ADDR +#define GET_ADDR() GET_LE16( &INSTR( 1, pc ) ) + +//////////////////////////////////////// ED prefix + { + case 0xED: + pc++; + s_time += (clock_table + 256) [data] >> 4; + switch ( data ) + { + { + int temp; + case 0x72: // SBC HL,SP + case 0x7A: // ADC HL,SP + temp = sp; + if ( 0 ) + case 0x42: // SBC HL,BC + case 0x52: // SBC HL,DE + case 0x62: // SBC HL,HL + case 0x4A: // ADC HL,BC + case 0x5A: // ADC HL,DE + case 0x6A: // ADC HL,HL + temp = R16( data >> 3 & 6, 1, 0 ); + int sum = temp + (flags & C01); + flags = ~data >> 2 & N02; + if ( flags ) + sum = -sum; + sum += r.w.hl; + temp ^= r.w.hl; + temp ^= sum; + flags +=(sum >> 16 & C01) + + (temp >> 8 & H10) + + (sum >> 8 & (S80 | F20 | F08)) + + ((temp + 0x8000) >> 14 & V04); + r.w.hl = sum; + if ( WORD( sum ) ) + goto loop; + flags += Z40; + goto loop; + } + + CASE8( 40, 48, 50, 58, 60, 68, 70, 78 ):{// IN r,(C) + int temp = IN_PORT( r.w.bc ); + R8( data >> 3, 8 ) = temp; + flags = (flags & C01) + SZ28P( temp ); + goto loop; + } + + case 0x71: // OUT (C),0 + r.b.flags = 0; + CASE7( 41, 49, 51, 59, 61, 69, 79 ): // OUT (C),r + OUT_PORT( r.w.bc, R8( data >> 3, 8 ) ); + goto loop; + + { + int temp; + case 0x73: // LD (ADDR),SP + temp = sp; + if ( 0 ) + case 0x43: // LD (ADDR),BC + case 0x53: // LD (ADDR),DE + temp = R16( data, 4, 0x43 ); + int addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, temp ); + goto loop; + } + + case 0x4B: // LD BC,(ADDR) + case 0x5B:{// LD DE,(ADDR) + int addr = GET_ADDR(); + pc += 2; + R16( data, 4, 0x4B ) = READ_WORD( addr ); + goto loop; + } + + case 0x7B:{// LD SP,(ADDR) + int addr = GET_ADDR(); + pc += 2; + sp = READ_WORD( addr ); + goto loop; + } + + case 0x67:{// RRD + int temp = READ_MEM( r.w.hl ); + WRITE_MEM( r.w.hl, ((r.b.a << 4) + (temp >> 4)) ); + temp = (r.b.a & 0xF0) + (temp & 0x0F); + flags = (flags & C01) + SZ28P( temp ); + r.b.a = temp; + goto loop; + } + + case 0x6F:{// RLD + int temp = READ_MEM( r.w.hl ); + WRITE_MEM( r.w.hl, ((temp << 4) + (r.b.a & 0x0F)) ); + temp = (r.b.a & 0xF0) + (temp >> 4); + flags = (flags & C01) + SZ28P( temp ); + r.b.a = temp; + goto loop; + } + + CASE8( 44, 4C, 54, 5C, 64, 6C, 74, 7C ): // NEG + opcode = 0x10; // flag to do SBC instead of ADC + flags &= ~C01; + data = r.b.a; + r.b.a = 0; + goto adc_data; + + { + int inc; + case 0xA9: // CPD + case 0xB9: // CPDR + inc = -1; + if ( 0 ) + case 0xA1: // CPI + case 0xB1: // CPIR + inc = +1; + int addr = r.w.hl; + r.w.hl = addr + inc; + int temp = READ_MEM( addr ); + + int result = r.b.a - temp; + flags = (flags & C01) + N02 + + ((((temp ^ r.b.a) & H10) ^ result) & (S80 | H10)); + + if ( !BYTE( result ) ) + flags += Z40; + result -= (flags & H10) >> 4; + flags += result & F08; + flags += result << 4 & F20; + if ( !--r.w.bc ) + goto loop; + + flags += V04; + if ( flags & Z40 || data < 0xB0 ) + goto loop; + + pc -= 2; + s_time += 5; + goto loop; + } + + { + int inc; + case 0xA8: // LDD + case 0xB8: // LDDR + inc = -1; + if ( 0 ) + case 0xA0: // LDI + case 0xB0: // LDIR + inc = +1; + int addr = r.w.hl; + r.w.hl = addr + inc; + int temp = READ_MEM( addr ); + + addr = r.w.de; + r.w.de = addr + inc; + WRITE_MEM( addr, temp ); + + temp += r.b.a; + flags = (flags & (S80 | Z40 | C01)) + + (temp & F08) + (temp << 4 & F20); + if ( !--r.w.bc ) + goto loop; + + flags += V04; + if ( data < 0xB0 ) + goto loop; + + pc -= 2; + s_time += 5; + goto loop; + } + + { + int inc; + case 0xAB: // OUTD + case 0xBB: // OTDR + inc = -1; + if ( 0 ) + case 0xA3: // OUTI + case 0xB3: // OTIR + inc = +1; + int addr = r.w.hl; + r.w.hl = addr + inc; + int temp = READ_MEM( addr ); + + int b = --r.b.b; + flags = (temp >> 6 & N02) + SZ28( b ); + if ( b && data >= 0xB0 ) + { + pc -= 2; + s_time += 5; + } + + OUT_PORT( r.w.bc, temp ); + goto loop; + } + + { + int inc; + case 0xAA: // IND + case 0xBA: // INDR + inc = -1; + if ( 0 ) + case 0xA2: // INI + case 0xB2: // INIR + inc = +1; + + int addr = r.w.hl; + r.w.hl = addr + inc; + + int temp = IN_PORT( r.w.bc ); + + int b = --r.b.b; + flags = (temp >> 6 & N02) + SZ28( b ); + if ( b && data >= 0xB0 ) + { + pc -= 2; + s_time += 5; + } + + WRITE_MEM( addr, temp ); + goto loop; + } + + case 0x47: // LD I,A + R.i = r.b.a; + goto loop; + + case 0x4F: // LD R,A + SET_R( r.b.a ); + dprintf( "LD R,A not supported\n" ); + warning = true; + goto loop; + + case 0x57: // LD A,I + r.b.a = R.i; + goto ld_ai_common; + + case 0x5F: // LD A,R + r.b.a = GET_R(); + dprintf( "LD A,R not supported\n" ); + warning = true; + ld_ai_common: + flags = (flags & C01) + SZ28( r.b.a ) + (R.iff2 << 2 & V04); + goto loop; + + CASE8( 45, 4D, 55, 5D, 65, 6D, 75, 7D ): // RETI/RETN + R.iff1 = R.iff2; + goto ret_taken; + + case 0x46: case 0x4E: case 0x66: case 0x6E: // IM 0 + R.im = 0; + goto loop; + + case 0x56: case 0x76: // IM 1 + R.im = 1; + goto loop; + + case 0x5E: case 0x7E: // IM 2 + R.im = 2; + goto loop; + + default: + dprintf( "Opcode $ED $%02X not supported\n", data ); + warning = true; + goto loop; + } + assert( false ); + } + +//////////////////////////////////////// DD/FD prefix + { + int ixy; + case 0xDD: + ixy = ix; + goto ix_prefix; + case 0xFD: + ixy = iy; + ix_prefix: + pc++; + int data2 = READ_CODE( pc ); + s_time += (clock_table + 256) [data] & 0x0F; + switch ( data ) + { + // TODO: more efficient way of avoid negative address + // TODO: avoid using this as argument to READ_MEM() since it is evaluated twice + #define IXY_DISP( ixy, disp ) WORD( (ixy ) + (disp)) + + #define SET_IXY( in ) if ( opcode == 0xDD ) ix = in; else iy = in; + + // ADD/ADC/SUB/SBC + + case 0x96: // SUB (IXY+disp) + case 0x86: // ADD (IXY+disp) + flags &= ~C01; + case 0x9E: // SBC (IXY+disp) + case 0x8E: // ADC (IXY+disp) + pc++; + opcode = data; + data = READ_MEM( IXY_DISP( ixy, SBYTE( data2 ) ) ); + goto adc_data; + + case 0x94: // SUB HXY + case 0x84: // ADD HXY + flags &= ~C01; + case 0x9C: // SBC HXY + case 0x8C: // ADC HXY + opcode = data; + data = ixy >> 8; + goto adc_data; + + case 0x95: // SUB LXY + case 0x85: // ADD LXY + flags &= ~C01; + case 0x9D: // SBC LXY + case 0x8D: // ADC LXY + opcode = data; + data = BYTE( ixy ); + goto adc_data; + + { + int temp; + case 0x39: // ADD IXY,SP + temp = sp; + goto add_ixy_data; + + case 0x29: // ADD IXY,HL + temp = ixy; + goto add_ixy_data; + + case 0x09: // ADD IXY,BC + case 0x19: // ADD IXY,DE + temp = R16( data, 4, 0x09 ); + add_ixy_data: { + int sum = ixy + temp; + temp ^= ixy; + ixy = WORD( sum ); + flags = (flags & (S80 | Z40 | V04)) + + (sum >> 16) + + (sum >> 8 & (F20 | F08)) + + ((temp ^ sum) >> 8 & H10); + goto set_ixy; + } + } + + // AND + case 0xA6: // AND (IXY+disp) + pc++; + data = READ_MEM( IXY_DISP( ixy, SBYTE( data2 ) ) ); + goto and_data; + + case 0xA4: // AND HXY + data = ixy >> 8; + goto and_data; + + case 0xA5: // AND LXY + data = BYTE( ixy ); + goto and_data; + + // OR + case 0xB6: // OR (IXY+disp) + pc++; + data = READ_MEM( IXY_DISP( ixy, SBYTE( data2 ) ) ); + goto or_data; + + case 0xB4: // OR HXY + data = ixy >> 8; + goto or_data; + + case 0xB5: // OR LXY + data = BYTE( ixy ); + goto or_data; + + // XOR + case 0xAE: // XOR (IXY+disp) + pc++; + data = READ_MEM( IXY_DISP( ixy, SBYTE( data2 ) ) ); + goto xor_data; + + case 0xAC: // XOR HXY + data = ixy >> 8; + goto xor_data; + + case 0xAD: // XOR LXY + data = BYTE( ixy ); + goto xor_data; + + // CP + case 0xBE: // CP (IXY+disp) + pc++; + data = READ_MEM( IXY_DISP( ixy, SBYTE( data2 ) ) ); + goto cp_data; + + case 0xBC: // CP HXY + data = ixy >> 8; + goto cp_data; + + case 0xBD: // CP LXY + data = BYTE( ixy ); + goto cp_data; + + // LD + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // LD (IXY+disp),r + data = R8( data, 0x70 ); + if ( 0 ) + case 0x36: // LD (IXY+disp),imm + pc++, data = READ_CODE( pc ); + pc++; + WRITE_MEM( IXY_DISP( ixy, SBYTE( data2 ) ), data ); + goto loop; + + CASE5( 44, 4C, 54, 5C, 7C ): // LD r,HXY + R8( data >> 3, 8 ) = ixy >> 8; + goto loop; + + case 0x64: // LD HXY,HXY + case 0x6D: // LD LXY,LXY + goto loop; + + CASE5( 45, 4D, 55, 5D, 7D ): // LD r,LXY + R8( data >> 3, 8 ) = ixy; + goto loop; + + CASE7( 46, 4E, 56, 5E, 66, 6E, 7E ): // LD r,(IXY+disp) + pc++; + R8( data >> 3, 8 ) = READ_MEM( IXY_DISP( ixy, SBYTE( data2 ) ) ); + goto loop; + + case 0x26: // LD HXY,imm + pc++; + goto ld_hxy_data; + + case 0x65: // LD HXY,LXY + data2 = BYTE( ixy ); + goto ld_hxy_data; + + CASE5( 60, 61, 62, 63, 67 ): // LD HXY,r + data2 = R8( data, 0x60 ); + ld_hxy_data: + ixy = BYTE( ixy ) + (data2 << 8); + goto set_ixy; + + case 0x2E: // LD LXY,imm + pc++; + goto ld_lxy_data; + + case 0x6C: // LD LXY,HXY + data2 = ixy >> 8; + goto ld_lxy_data; + + CASE5( 68, 69, 6A, 6B, 6F ): // LD LXY,r + data2 = R8( data, 0x68 ); + ld_lxy_data: + ixy = (ixy & 0xFF00) + data2; + set_ixy: + if ( opcode == 0xDD ) + { + ix = ixy; + goto loop; + } + iy = ixy; + goto loop; + + case 0xF9: // LD SP,IXY + sp = ixy; + goto loop; + + case 0x22:{// LD (ADDR),IXY + int addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, ixy ); + goto loop; + } + + case 0x21: // LD IXY,imm + ixy = GET_ADDR(); + pc += 2; + goto set_ixy; + + case 0x2A:{// LD IXY,(addr) + int addr = GET_ADDR(); + ixy = READ_WORD( addr ); + pc += 2; + goto set_ixy; + } + + // DD/FD CB prefix + case 0xCB: { + data = IXY_DISP( ixy, SBYTE( data2 ) ); + pc++; + data2 = READ_CODE( pc ); + pc++; + switch ( data2 ) + { + case 0x06: goto rlc_data_addr; // RLC (IXY) + case 0x16: goto rl_data_addr; // RL (IXY) + case 0x26: goto sla_data_addr; // SLA (IXY) + case 0x36: goto sll_data_addr; // SLL (IXY) + case 0x0E: goto rrc_data_addr; // RRC (IXY) + case 0x1E: goto rr_data_addr; // RR (IXY) + case 0x2E: goto sra_data_addr; // SRA (IXY) + case 0x3E: goto srl_data_addr; // SRL (IXY) + + CASE8( 46, 4E, 56, 5E, 66, 6E, 76, 7E ):{// BIT b,(IXY+disp) + int temp = READ_MEM( data ); + temp = temp & (1 << (data2 >> 3 & 7)); + flags = (flags & C01) + H10 + (temp & S80); + flags += (unsigned) --temp >> 8 & (Z40 | P04); + goto loop; + } + + CASE8( 86, 8E, 96, 9E, A6, AE, B6, BE ): // RES b,(IXY+disp) + CASE8( C6, CE, D6, DE, E6, EE, F6, FE ):{// SET b,(IXY+disp) + int temp = READ_MEM( data ); + int bit = 1 << (data2 >> 3 & 7); + temp |= bit; // SET + if ( !(data2 & 0x40) ) + temp ^= bit; // RES + WRITE_MEM( data, temp ); + goto loop; + } + + default: + dprintf( "Opcode $%02X $CB $%02X not supported\n", opcode, data2 ); + warning = true; + goto loop; + } + assert( false ); + } + + // INC/DEC + case 0x23: // INC IXY + ixy = WORD( ixy + 1 ); + goto set_ixy; + + case 0x2B: // DEC IXY + ixy = WORD( ixy - 1 ); + goto set_ixy; + + case 0x34: // INC (IXY+disp) + ixy = IXY_DISP( ixy, SBYTE( data2 ) ); + pc++; + data = READ_MEM( ixy ) + 1; + WRITE_MEM( ixy, data ); + goto inc_set_flags; + + case 0x35: // DEC (IXY+disp) + ixy = IXY_DISP( ixy, SBYTE( data2 ) ); + pc++; + data = READ_MEM( ixy ) - 1; + WRITE_MEM( ixy, data ); + goto dec_set_flags; + + case 0x24: // INC HXY + ixy = WORD( ixy + 0x100 ); + data = ixy >> 8; + goto inc_xy_common; + + case 0x2C: // INC LXY + data = BYTE( ixy + 1 ); + ixy = (ixy & 0xFF00) + data; + inc_xy_common: + if ( opcode == 0xDD ) + { + ix = ixy; + goto inc_set_flags; + } + iy = ixy; + goto inc_set_flags; + + case 0x25: // DEC HXY + ixy = WORD( ixy - 0x100 ); + data = ixy >> 8; + goto dec_xy_common; + + case 0x2D: // DEC LXY + data = BYTE( ixy - 1 ); + ixy = (ixy & 0xFF00) + data; + dec_xy_common: + if ( opcode == 0xDD ) + { + ix = ixy; + goto dec_set_flags; + } + iy = ixy; + goto dec_set_flags; + + // PUSH/POP + case 0xE5: // PUSH IXY + data = ixy; + goto push_data; + + case 0xE1:{// POP IXY + ixy = READ_WORD( sp ); + sp = WORD( sp + 2 ); + goto set_ixy; + } + + // Misc + + case 0xE9: // JP (IXY) + pc = ixy; + goto loop; + + case 0xE3:{// EX (SP),IXY + int temp = READ_WORD( sp ); + WRITE_WORD( sp, ixy ); + ixy = temp; + goto set_ixy; + } + + default: + dprintf( "Unnecessary DD/FD prefix encountered\n" ); + warning = true; + pc--; + goto loop; + } + assert( false ); + } + + } + dprintf( "Unhandled main opcode: $%02X\n", opcode ); + assert( false ); + +#ifdef IDLE_ADDR +hit_idle_addr: + s_time -= 11; + goto out_of_time; +#endif +halt: + s_time &= 3; // increment by multiple of 4 +out_of_time: + pc--; + + r.b.flags = flags; + R.ix = ix; + R.iy = iy; + R.sp = sp; + R.pc = pc; + R.b = r.b; + + cpu->cpu_state_.base = s.base; + cpu->cpu_state_.time = s_time; + cpu->cpu_state = &cpu->cpu_state_; +} diff --git a/apps/codecs/nsf.c b/apps/codecs/nsf.c index d626d528bf..a556f75b27 100644 --- a/apps/codecs/nsf.c +++ b/apps/codecs/nsf.c @@ -1,4378 +1,68 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * - * Copyright (C) 2006 Adam Gashlin (hcs) - * Copyright (C) 2004 Disch - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - ****************************************************************************/ -/* - * This is a perversion of Disch's excellent NotSoFatso. - */ +/* Ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */ -#include "codeclib.h" -#include "inttypes.h" -#include "system.h" +#define GME_NSF_TYPE -CODEC_HEADER - -#if (CONFIG_CPU == MCF5250) -#define ICODE_INSTEAD_OF_INLINE -/* Enough IRAM to move additional data and code to it. */ -#define IBSS_ATTR_NSF_LARGE_IRAM IBSS_ATTR -#define ICONST_ATTR_NSF_LARGE_IRAM ICONST_ATTR - -#elif (CONFIG_CPU == PP5022) || (CONFIG_CPU == PP5024) -#define ICODE_INSTEAD_OF_INLINE -/* Enough IRAM to move additional data and code to it. */ -#define IBSS_ATTR_NSF_LARGE_IRAM IBSS_ATTR -#define ICONST_ATTR_NSF_LARGE_IRAM ICONST_ATTR - -#elif defined(CPU_S5L870X) -#define ICODE_INSTEAD_OF_INLINE -/* Very large IRAM. Move even more data to it. */ -#define IBSS_ATTR_NSF_LARGE_IRAM IBSS_ATTR -#define ICONST_ATTR_NSF_LARGE_IRAM ICONST_ATTR - -#else -#define ICODE_INSTEAD_OF_INLINE -/* Not enough IRAM available. */ -#define IBSS_ATTR_NSF_LARGE_IRAM -#define ICONST_ATTR_NSF_LARGE_IRAM -#endif - -/* Maximum number of bytes to process in one iteration */ -#define WAV_CHUNK_SIZE (1024*2) - -static int16_t samples[WAV_CHUNK_SIZE] IBSS_ATTR MEM_ALIGN_ATTR; - -#define ZEROMEMORY(addr,size) memset(addr,0,size) - -/* simple profiling with USEC_TIMER - -#define NSF_PROFILE - -*/ - -#ifdef NSF_PROFILE - -#define CREATE_TIMER(name) static uint32_t nsf_timer_##name##_start,\ - nsf_timer_##name##_total -#define ENTER_TIMER(name) nsf_timer_##name##_start=USEC_TIMER -#define EXIT_TIMER(name) nsf_timer_##name##_total+=\ - (USEC_TIMER-nsf_timer_##name##_start) -#define READ_TIMER(name) (nsf_timer_##name##_total) -#define RESET_TIMER(name) nsf_timer_##name##_total=0 - -#define PRINT_TIMER_PCT(bname,tname,nstr) ci->fdprintf( - logfd,"%10ld ",READ_TIMER(bname));\ - ci->fdprintf(logfd,"(%3d%%) " nstr "\t",\ - ((uint64_t)READ_TIMER(bname))*100/READ_TIMER(tname)) - -CREATE_TIMER(total); -CREATE_TIMER(cpu); -CREATE_TIMER(apu); -CREATE_TIMER(squares); -CREATE_TIMER(tnd); -CREATE_TIMER(tnd_enter); -CREATE_TIMER(tnd_tri); -CREATE_TIMER(tnd_noise); -CREATE_TIMER(tnd_dmc); -CREATE_TIMER(fds); -CREATE_TIMER(frame); -CREATE_TIMER(mix); - -void reset_profile_timers(void) { - RESET_TIMER(total); - RESET_TIMER(cpu); - RESET_TIMER(apu); - RESET_TIMER(squares); - RESET_TIMER(tnd); - RESET_TIMER(tnd_enter); - RESET_TIMER(tnd_tri); - RESET_TIMER(tnd_noise); - RESET_TIMER(tnd_dmc); - RESET_TIMER(fds); - RESET_TIMER(frame); - RESET_TIMER(mix); -} - -int logfd=-1; - -void print_timers(char * path, int track) { - logfd = ci->open("/nsflog.txt",O_WRONLY|O_CREAT|O_APPEND, 0666); - ci->fdprintf(logfd,"%s[%d]:\t",path,track); - ci->fdprintf(logfd,"%10ld total\t",READ_TIMER(total)); - PRINT_TIMER_PCT(cpu,total,"CPU"); - PRINT_TIMER_PCT(apu,total,"APU"); - ci->fdprintf(logfd,"\n\t"); - PRINT_TIMER_PCT(squares,apu,"squares"); - PRINT_TIMER_PCT(frame,apu,"frame"); - PRINT_TIMER_PCT(mix,apu,"mix"); - PRINT_TIMER_PCT(fds,apu,"FDS"); - PRINT_TIMER_PCT(tnd,apu,"tnd"); - ci->fdprintf(logfd,"\n\t\t"); - PRINT_TIMER_PCT(tnd_enter,tnd,"enter"); - PRINT_TIMER_PCT(tnd_tri,tnd,"triangle"); - PRINT_TIMER_PCT(tnd_noise,tnd,"noise"); - PRINT_TIMER_PCT(tnd_dmc,tnd,"DMC"); - ci->fdprintf(logfd,"\n"); - - ci->close(logfd); - logfd=-1; -} - -#else - -#define CREATE_TIMER(name) -#define ENTER_TIMER(name) -#define EXIT_TIMER(name) -#define READ_TIMER(name) -#define RESET_TIMER(name) -#define print_timers(path,track) -#define reset_profile_timers() - -#endif - -/* proper handling of multibyte values */ -#ifdef ROCKBOX_LITTLE_ENDIAN -union TWIN -{ - uint16_t W; - struct{ uint8_t l; uint8_t h; } B; -}; - -union QUAD -{ - uint32_t D; - struct{ uint8_t l; uint8_t h; uint16_t w; } B; -}; -#else - -union TWIN -{ - uint16_t W; - struct{ uint8_t h; uint8_t l; } B; -}; - -union QUAD -{ - uint32_t D; - struct{uint16_t w; uint8_t h; uint8_t l; } B; -}; - -#endif - -#define NTSC_FREQUENCY 1789772.727273f -#define PAL_FREQUENCY 1652097.692308f -#define NTSC_NMIRATE 60.098814f -#define PAL_NMIRATE 50.006982f - -#define NES_FREQUENCY 21477270 -#define NTSC_FRAME_COUNTER_FREQ (NTSC_FREQUENCY / (NES_FREQUENCY / 89490.0f)) -#define PAL_FRAME_COUNTER_FREQ (PAL_FREQUENCY / (NES_FREQUENCY / 89490.0f)) - -/****************** tables */ -static const int32_t ModulationTable[8] ICONST_ATTR = {0,1,2,4,0,-4,-2,-1}; -static const uint16_t DMC_FREQ_TABLE[2][0x10] ICONST_ATTR_NSF_LARGE_IRAM = { - /* NTSC */ - {0x1AC,0x17C,0x154,0x140,0x11E,0x0FE,0x0E2,0x0D6,0x0BE,0x0A0,0x08E,0x080, - 0x06A,0x054,0x048,0x036}, - /* PAL */ - {0x18C,0x160,0x13A,0x128,0x108,0x0EA,0x0D0,0x0C6,0x0B0,0x094,0x082,0x076, - 0x062,0x04E,0x042,0x032} -}; - -static const uint8_t DUTY_CYCLE_TABLE[4] ICONST_ATTR_NSF_LARGE_IRAM = { - 2,4,8,12 -}; - -static const uint8_t LENGTH_COUNTER_TABLE[0x20] ICONST_ATTR_NSF_LARGE_IRAM = { - 0x0A,0xFE,0x14,0x02,0x28,0x04,0x50,0x06,0xA0,0x08,0x3C,0x0A,0x0E,0x0C,0x1A, - 0x0E,0x0C,0x10,0x18,0x12,0x30,0x14,0x60,0x16,0xC0,0x18,0x48,0x1A,0x10,0x1C, - 0x20,0x1E -}; - -static const uint16_t NOISE_FREQ_TABLE[0x10] ICONST_ATTR_NSF_LARGE_IRAM = { - 0x004,0x008,0x010,0x020,0x040,0x060,0x080,0x0A0,0x0CA,0x0FE,0x17C,0x1FC, - 0x2FA,0x3F8,0x7F2,0xFE4 -}; - -/****************** NSF loading ******************/ - -/* file format structs (both are little endian) */ - -struct NESM_HEADER -{ - uint32_t nHeader; - uint8_t nHeaderExtra; - uint8_t nVersion; - uint8_t nTrackCount; - uint8_t nInitialTrack; - uint16_t nLoadAddress; - uint16_t nInitAddress; - uint16_t nPlayAddress; - uint8_t szGameTitle[32]; - uint8_t szArtist[32]; - uint8_t szCopyright[32]; - uint16_t nSpeedNTSC; - uint8_t nBankSwitch[8]; - uint16_t nSpeedPAL; - uint8_t nNTSC_PAL; - uint8_t nExtraChip; - uint8_t nExpansion[4]; -}; - -struct NSFE_INFOCHUNK -{ - uint16_t nLoadAddress; - uint16_t nInitAddress; - uint16_t nPlayAddress; - uint8_t nIsPal; - uint8_t nExt; - uint8_t nTrackCount; - uint8_t nStartingTrack; -}; - -static int32_t LoadFile(uint8_t *,size_t); - -static int32_t LoadFile_NESM(uint8_t *,size_t); -static int32_t LoadFile_NSFE(uint8_t *,size_t); - -/* NSF file info */ - -/* basic NSF info */ -static int32_t bIsExtended=0; /* 0 = NSF, 1 = NSFE */ -static uint8_t nIsPal=0; /* 0 = NTSC, 1 = PAL, 2,3 = mixed - NTSC/PAL (interpretted as NTSC) */ -static int32_t nfileLoadAddress=0; /* The address to which the NSF code is - loaded */ -static int32_t nfileInitAddress=0; /* The address of the Init routine - (called at track change) */ -static int32_t nfilePlayAddress=0; /* The address of the Play routine - (called several times a second) */ -static uint8_t nChipExtensions=0; /* Bitwise representation of the - external chips used by this NSF. */ - -/* old NESM speed stuff (blarg) */ -static int32_t nNTSC_PlaySpeed=0; -static int32_t nPAL_PlaySpeed=0; - -/* track info */ -/* The number of tracks in the NSF (1 = 1 track, 5 = 5 tracks, etc) */ -static int32_t nTrackCount=0; -/* The initial track (ZERO BASED: 0 = 1st track, 4 = 5th track, etc) */ -static int32_t nInitialTrack=0; - -/* nsf data */ -static uint8_t* pDataBuffer=0; /* the buffer containing NSF code. */ -static int32_t nDataBufferSize=0; /* the size of the above buffer. */ - -/* playlist */ -static uint8_t nPlaylist[256]; /* Each entry is the zero based index of - the song to play */ -static int32_t nPlaylistSize=0; /* number of tracks in the playlist */ - -/* track time / fade */ -static int32_t nTrackTime[256]; /* track times -1 if no track times - specified */ -static int32_t nTrackFade[256]; /* track fade times -1 if none are - specified */ - -/* string info */ -static uint8_t szGameTitle[0x101]; -static uint8_t szArtist[0x101]; -static uint8_t szCopyright[0x101]; -static uint8_t szRipper[0x101]; - -/* bankswitching info */ -static uint8_t nBankswitch[8]={0}; /* The initial bankswitching registers - needed for some NSFs. If the NSF does - not use bankswitching, these values - will all be zero */ - -static int32_t LoadFile(uint8_t * inbuffer, size_t size) -{ - if(!inbuffer) return -1; - - int32_t ret = -1; - - if(!memcmp(inbuffer,"NESM",4)) ret = LoadFile_NESM(inbuffer,size); - if(!memcmp(inbuffer,"NSFE",4)) ret = LoadFile_NSFE(inbuffer,size); - - /* - * Snake's revenge puts '00' for the initial track, - * which (after subtracting 1) makes it 256 or -1 (bad!) - * This prevents that crap - */ - if(nInitialTrack >= nTrackCount) - nInitialTrack = 0; - if(nInitialTrack < 0) - nInitialTrack = 0; - - /* if there's no tracks... this is a crap NSF */ - if(nTrackCount < 1) - { - return -1; - } - - return ret; -} - -static int32_t LoadFile_NESM(uint8_t* inbuffer, size_t size) -{ - uint8_t ignoreversion=1; - uint8_t needdata=1; - - /* read the info */ - struct NESM_HEADER hdr; - - memcpy(&hdr,inbuffer,sizeof(hdr)); - - /* confirm the header */ - if(memcmp("NESM",&(hdr.nHeader),4)) return -1; - if(hdr.nHeaderExtra != 0x1A) return -1; - /* stupid NSFs claim to be above version 1 >_> */ - if((!ignoreversion) && (hdr.nVersion != 1)) return -1; - - /* - * NESM is generally easier to work with (but limited!) - * just move the data over from NESM_HEADER over to our member data - */ - - bIsExtended = 0; - nIsPal = hdr.nNTSC_PAL & 0x03; - nPAL_PlaySpeed = letoh16(hdr.nSpeedPAL); - nNTSC_PlaySpeed = letoh16(hdr.nSpeedNTSC); - nfileLoadAddress = letoh16(hdr.nLoadAddress); - nfileInitAddress = letoh16(hdr.nInitAddress); - nfilePlayAddress = letoh16(hdr.nPlayAddress); - nChipExtensions = hdr.nExtraChip; - - - nTrackCount = hdr.nTrackCount; - nInitialTrack = hdr.nInitialTrack - 1; - - memcpy(nBankswitch,hdr.nBankSwitch,8); - - memcpy(szGameTitle,hdr.szGameTitle,32); - memcpy(szArtist ,hdr.szArtist ,32); - memcpy(szCopyright,hdr.szCopyright,32); - - /* read the NSF data */ - if(needdata) - { - pDataBuffer=inbuffer+0x80; - nDataBufferSize=size-0x80; - } - - /* if we got this far... it was a successful read */ - return 0; -} - -static int32_t LoadFile_NSFE(uint8_t* inbuffer, size_t size) -{ - /* the vars we'll be using */ - uint32_t nChunkType; - int32_t nChunkSize; - int32_t nChunkUsed; - int32_t i; - uint8_t * nDataPos = 0; - uint8_t bInfoFound = 0; - uint8_t bEndFound = 0; - uint8_t bBankFound = 0; - nPlaylistSize=-1; - - struct NSFE_INFOCHUNK info; - ZEROMEMORY(&info,sizeof(struct NSFE_INFOCHUNK)); - ZEROMEMORY(nBankswitch,8); - info.nTrackCount = 1; /* default values */ - - if (size < 8) return -1; /* must have at least NSFE,NEND */ - - /* confirm the header! */ - memcpy(&nChunkType,inbuffer,4); - inbuffer+=4; - if(memcmp(&nChunkType,"NSFE",4)) return -1; - - for (i=0;i<256;i++) { - nTrackTime[i]=-1; - nTrackFade[i]=-1; - } - - /* begin reading chunks */ - while(!bEndFound) - { - memcpy(&nChunkSize,inbuffer,4); - nChunkSize=letoh32(nChunkSize); - inbuffer+=4; - memcpy(&nChunkType,inbuffer,4); - inbuffer+=4; - - if(!memcmp(&nChunkType,"INFO",4)) { - /* only one info chunk permitted */ - if(bInfoFound) return -1; - if(nChunkSize < 8) return -1; /* minimum size */ - - bInfoFound = 1; - nChunkUsed = MIN((int32_t)sizeof(struct NSFE_INFOCHUNK), - nChunkSize); - - memcpy(&info,inbuffer,nChunkUsed); - inbuffer+=nChunkSize; - - bIsExtended = 1; - nIsPal = info.nIsPal & 3; - nfileLoadAddress = letoh16(info.nLoadAddress); - nfileInitAddress = letoh16(info.nInitAddress); - nfilePlayAddress = letoh16(info.nPlayAddress); - nChipExtensions = info.nExt; - nTrackCount = info.nTrackCount; - nInitialTrack = info.nStartingTrack; - - nPAL_PlaySpeed = (uint16_t)(1000000 / PAL_NMIRATE); - nNTSC_PlaySpeed = (uint16_t)(1000000 / NTSC_NMIRATE); - } else if (!memcmp(&nChunkType,"DATA",4)) { - if(!bInfoFound) return -1; - if(nDataPos) return -1; - if(nChunkSize < 1) return -1; - - nDataBufferSize = nChunkSize; - nDataPos = inbuffer; - - inbuffer+=nChunkSize; - } else if (!memcmp(&nChunkType,"NEND",4)) { - bEndFound = 1; - } else if (!memcmp(&nChunkType,"time",4)) { - if(!bInfoFound) return -1; - for (nChunkUsed=0; nChunkUsed < MIN(nChunkSize / 4,nTrackCount); - nChunkUsed++,inbuffer+=4) { - nTrackTime[nChunkUsed]= - ((uint32_t)inbuffer[0])| - ((uint32_t)inbuffer[1]<<8)| - ((uint32_t)inbuffer[2]<<16)| - ((uint32_t)inbuffer[3]<<24); - } - - inbuffer+=nChunkSize-(nChunkUsed*4); - - /* negative signals to use default time */ - for(; nChunkUsed < nTrackCount; nChunkUsed++) - nTrackTime[nChunkUsed] = -1; - } else if (!memcmp(&nChunkType,"fade",4)) { - if(!bInfoFound) return -1; - for (nChunkUsed=0; nChunkUsed < MIN(nChunkSize / 4,nTrackCount); - nChunkUsed++,inbuffer+=4) { - nTrackFade[nChunkUsed]= - ((uint32_t)inbuffer[0])| - ((uint32_t)inbuffer[1]<<8)| - ((uint32_t)inbuffer[2]<<16)| - ((uint32_t)inbuffer[3]<<24); - } - - inbuffer+=nChunkSize-(nChunkUsed*4); - - /* negative signals to use default time */ - for(; nChunkUsed < nTrackCount; nChunkUsed++) - nTrackFade[nChunkUsed] = -1; - } else if (!memcmp(&nChunkType,"BANK",4)) { - if(bBankFound) return -1; - - bBankFound = 1; - nChunkUsed = MIN(8,nChunkSize); - memcpy(nBankswitch,inbuffer,nChunkUsed); - - inbuffer+=nChunkSize; - } else if (!memcmp(&nChunkType,"plst",4)) { - - nPlaylistSize = nChunkSize; - if(nPlaylistSize >= 1) { - - memcpy(nPlaylist,inbuffer,nChunkSize); - inbuffer+=nChunkSize; - } - } else if (!memcmp(&nChunkType,"auth",4)) { - uint8_t* ptr; - - ptr = inbuffer; - - uint8_t* ar[4] = {szGameTitle,szArtist,szCopyright,szRipper}; - int32_t i; - for(i = 0; (ptr-inbuffer)>24; /* check the first byte */ - /* chunk is vital... don't continue */ - if((nChunkType >= 'A') && (nChunkType <= 'Z')) - return -1; - /* otherwise, just skip it */ - inbuffer+=nChunkSize; - } /* end if series */ - } /* end while */ - - /* - * if we exited the while loop without a 'return', we must have hit an NEND - * chunk if this is the case, the file was layed out as it was expected. - * now.. make sure we found both an info chunk, AND a data chunk... since - * these are minimum requirements for a valid NSFE file - */ - - if(!bInfoFound) return -1; - if(!nDataPos) return -1; - - /* if both those chunks existed, this file is valid. - Load the data if it's needed */ - - pDataBuffer=nDataPos; - - /* return success! */ - return 0; -} - - -/****************** Audio Device Structures ******************/ - -struct FDSWave -{ - /* Envelope Unit */ - uint8_t bEnvelopeEnable; - uint8_t nEnvelopeSpeed; - - /* Volume Envelope */ - uint8_t nVolEnv_Mode; - uint8_t nVolEnv_Decay; - uint8_t nVolEnv_Gain; - int32_t nVolEnv_Timer; - int32_t nVolEnv_Count; - uint8_t nVolume; - uint8_t bVolEnv_On; - - /* Sweep Envenlope */ - uint8_t nSweep_Mode; - uint8_t nSweep_Decay; - int32_t nSweep_Timer; - int32_t nSweep_Count; - uint8_t nSweep_Gain; - uint8_t bSweepEnv_On; - - /* Effector / LFO / Modulation Unit */ - int32_t nSweepBias; - uint8_t bLFO_Enabled; - union TWIN nLFO_Freq; - /*float fLFO_Timer;*/ - /*float fLFO_Count;*/ - int32_t nLFO_Timer; /* -17.14*/ - int32_t nLFO_Count; /* -17.14*/ - uint8_t nLFO_Addr; - uint8_t nLFO_Table[0x40]; - uint8_t bLFO_On; - - /* Main Output */ - uint8_t nMainVolume; - uint8_t bEnabled; - union TWIN nFreq; - /*float fFreqCount;*/ - int32_t nFreqCount; /* -17.14 */ - uint8_t nMainAddr; - uint8_t nWaveTable[0x40]; - uint8_t bWaveWrite; - uint8_t bMain_On; - - /* Output and Downsampling */ - int32_t nMixL; - - /* Pop Reducer */ - uint8_t bPopReducer; - uint8_t nPopOutput; - int32_t nPopCount; - -}; -static int16_t FDS_nOutputTable_L[4][0x21][0x40] IBSS_ATTR_NSF_LARGE_IRAM MEM_ALIGN_ATTR; - -struct FME07Wave -{ - /* Frequency Control */ - union TWIN nFreqTimer; - int32_t nFreqCount; - - /* Channel Disabling */ - uint8_t bChannelEnabled; - - /* Volume */ - uint8_t nVolume; - - /* Duty Cycle */ - uint8_t nDutyCount; - - /* Output and Downsampling */ - int32_t nMixL; -}; - -static int16_t FME07_nOutputTable_L[0x10] IDATA_ATTR MEM_ALIGN_ATTR; - -struct N106Wave -{ - /* All Channel Stuff */ - - uint8_t nActiveChannels; - uint8_t bAutoIncrement; - uint8_t nCurrentAddress; - uint8_t nRAM[0x100]; /* internal memory for registers/wave data */ - int32_t nFrequencyLookupTable[8]; /* lookup tbl for freq conversions */ - - /* - * Individual channel stuff - */ - /* Wavelength / Frequency */ - union QUAD nFreqReg[8]; - int32_t nFreqTimer[8]; - int32_t nFreqCount[8]; - - /* Wave data length / remaining */ - uint8_t nWaveSize[8]; - uint8_t nWaveRemaining[8]; - - /* Wave data position */ - uint8_t nWavePosStart[8]; - uint8_t nWavePos[8]; - uint8_t nOutput[8]; - - /* Volume */ - uint8_t nVolume[8]; - - /* Pop Reducer */ - uint8_t nPreVolume[8]; - uint8_t nPopCheck[8]; - - /* Mixing */ - int32_t nMixL[8]; -}; - -static int16_t N106_nOutputTable_L[0x10][0x10] IBSS_ATTR_NSF_LARGE_IRAM MEM_ALIGN_ATTR; - -struct VRC6PulseWave -{ - - /* Frequency Control */ - union TWIN nFreqTimer; - int32_t nFreqCount; - - /* Flags */ - uint8_t bChannelEnabled; - uint8_t bDigitized; - - /* Volume */ - uint8_t nVolume; - - /* Duty Cycle */ - uint8_t nDutyCycle; - uint8_t nDutyCount; - - /* Output and Downsampling */ - int32_t nMixL; - -}; - -static int16_t VRC6Pulse_nOutputTable_L[0x10] IDATA_ATTR MEM_ALIGN_ATTR; - -struct VRC6SawWave -{ - - /* Frequency Control */ - union TWIN nFreqTimer; - int32_t nFreqCount; - - /* Flags */ - uint8_t bChannelEnabled; - - /* Phase Accumulator */ - uint8_t nAccumRate; - uint8_t nAccum; - uint8_t nAccumStep; - - /* Output and Downsampling */ - int32_t nMixL; - -}; - -static int16_t VRC6Saw_nOutputTable_L[0x20] IDATA_ATTR MEM_ALIGN_ATTR; - -struct Wave_Squares -{ - - /* Programmable Timer */ - union TWIN nFreqTimer[2]; - int32_t nFreqCount[2]; - - /* Length Counter */ - uint8_t nLengthCount[2]; - uint8_t bLengthEnabled[2]; - uint8_t bChannelEnabled[2]; - - /* Volume / Decay */ - uint8_t nVolume[2]; - uint8_t nDecayVolume[2]; - uint8_t bDecayEnable[2]; - uint8_t bDecayLoop[2]; - uint8_t nDecayTimer[2]; - uint8_t nDecayCount[2]; - - /* Sweep Unit */ - uint8_t bSweepEnable[2]; - uint8_t bSweepMode[2]; - uint8_t bSweepForceSilence[2]; - uint8_t nSweepTimer[2]; - uint8_t nSweepCount[2]; - uint8_t nSweepShift[2]; - - /* Duty Cycle */ - uint8_t nDutyCount[2]; - uint8_t nDutyCycle[2]; - - /* Output and Downsampling */ - int32_t nMixL; -}; - -static int16_t Squares_nOutputTable_L[0x10][0x10] IDATA_ATTR MEM_ALIGN_ATTR; - -struct Wave_TND -{ - - /* - * Triangle - */ - - /* Programmable Timer */ - union TWIN nTriFreqTimer; - int32_t nTriFreqCount; - - /* Length Counter */ - uint8_t nTriLengthCount; - uint8_t bTriLengthEnabled; - uint8_t bTriChannelEnabled; - - /* Linear Counter */ - uint8_t nTriLinearCount; - uint8_t nTriLinearLoad; - uint8_t bTriLinearHalt; - uint8_t bTriLinearControl; - - /* Tri-Step Generator / Output */ - uint8_t nTriStep; - uint8_t nTriOutput; - - /* - * Noise - */ - - /* Programmable Timer */ - uint16_t nNoiseFreqTimer; - int32_t nNoiseFreqCount; - - /* Length Counter */ - uint8_t nNoiseLengthCount; - uint8_t bNoiseLengthEnabled; - uint8_t bNoiseChannelEnabled; - - /* Volume / Decay */ - uint8_t nNoiseVolume; - uint8_t nNoiseDecayVolume; - uint8_t bNoiseDecayEnable; - uint8_t bNoiseDecayLoop; - uint8_t nNoiseDecayTimer; - uint8_t nNoiseDecayCount; - - /* Random Number Generator */ - uint16_t nNoiseRandomShift; - uint8_t bNoiseRandomMode; /* 1 = 32k, 6 = 93-bit */ - uint8_t bNoiseRandomOut; - - /* - * DMC - */ - - /* Play Mode */ - uint8_t bDMCLoop; - uint8_t bDMCIRQEnabled; - uint8_t bDMCIRQPending; - - /* Address / DMA */ - uint8_t nDMCDMABank_Load; - uint16_t nDMCDMAAddr_Load; - uint8_t nDMCDMABank; - uint16_t nDMCDMAAddr; - uint8_t* pDMCDMAPtr[8]; - - /* Length / Input */ - uint16_t nDMCLength; - uint16_t nDMCBytesRemaining; - uint8_t nDMCDelta; - uint8_t nDMCDeltaBit; - uint8_t bDMCDeltaSilent; - uint8_t nDMCSampleBuffer; - uint8_t bDMCSampleBufferEmpty; - - /* Frequency */ - uint16_t nDMCFreqTimer; - int32_t nDMCFreqCount; - - /* Output */ - uint8_t bDMCActive; - uint8_t nDMCOutput; - - int32_t nMixL; -}; - -/* channels */ -static struct Wave_Squares mWave_Squares IDATA_ATTR; /* Square channels 1 and 2 */ -static struct Wave_TND mWave_TND IDATA_ATTR; /* Triangle/Noise/DMC channels */ -static struct VRC6PulseWave mWave_VRC6Pulse[2] IDATA_ATTR; -static struct VRC6SawWave mWave_VRC6Saw IDATA_ATTR; -static struct N106Wave mWave_N106 IDATA_ATTR; -static struct FDSWave mWave_FDS IDATA_ATTR; -static struct FME07Wave mWave_FME07[3] IDATA_ATTR; /* FME-07's 3 pulse channels */ - - -/****************** MMC5 ******************/ -/* will include MMC5 sound channels some day, - currently only multiply is supported */ - -/****************** N106 (Disch loves this chip) ******************/ - -#ifdef ICODE_INSTEAD_OF_INLINE -static void Wave_N106_DoTicks(const int32_t ticks) ICODE_ATTR; -static void Wave_N106_DoTicks(const int32_t ticks) -#else -static inline void Wave_N106_DoTicks(const int32_t ticks); -static inline void Wave_N106_DoTicks(const int32_t ticks) -#endif -{ - register int32_t i; - - for(i = (7 - mWave_N106.nActiveChannels); i < 8; i++) - { - if(!mWave_N106.nFreqReg[i].D) - { - /* written frequency of zero will cause divide by zero error - makes me wonder if the formula was supposed to be Reg+1 */ - mWave_N106.nVolume[i] = mWave_N106.nPreVolume[i]; - continue; - } - - { - mWave_N106.nMixL[i] = - N106_nOutputTable_L[mWave_N106.nVolume[i]] - [mWave_N106.nOutput[i]]; - - if(mWave_N106.nFreqTimer[i] < 0) - mWave_N106.nFreqTimer[i] = - (mWave_N106.nFrequencyLookupTable[mWave_N106.nActiveChannels] / - mWave_N106.nFreqReg[i].D); - if(mWave_N106.nFreqCount[i] > mWave_N106.nFreqTimer[i]) - mWave_N106.nFreqCount[i] = mWave_N106.nFreqTimer[i]; - - mWave_N106.nFreqCount[i] -= ticks << 8; - while(mWave_N106.nFreqCount[i] <= 0) - { - mWave_N106.nFreqCount[i] += mWave_N106.nFreqTimer[i]; - if(mWave_N106.nWaveRemaining[i]) - { - mWave_N106.nWaveRemaining[i]--; - mWave_N106.nWavePos[i]++; - } - if(!mWave_N106.nWaveRemaining[i]) - { - mWave_N106.nWaveRemaining[i] = mWave_N106.nWaveSize[i]; - mWave_N106.nWavePos[i] = mWave_N106.nWavePosStart[i]; - if(mWave_N106.nVolume[i] != mWave_N106.nPreVolume[i]) - { - if(++mWave_N106.nPopCheck[i] >= 2) - { - mWave_N106.nPopCheck[i] = 0; - mWave_N106.nVolume[i] = mWave_N106.nPreVolume[i]; - } - } - } - - mWave_N106.nOutput[i] = - mWave_N106.nRAM[mWave_N106.nWavePos[i]]; - - if(!mWave_N106.nOutput[i]) - { - mWave_N106.nPopCheck[i] = 0; - mWave_N106.nVolume[i] = mWave_N106.nPreVolume[i]; - } - - } - } - } -} -/****************** VRC6 ******************/ - -#ifdef ICODE_INSTEAD_OF_INLINE -static void Wave_VRC6_DoTicks(const int32_t ticks) ICODE_ATTR; -static void Wave_VRC6_DoTicks(const int32_t ticks) -#else -static inline void Wave_VRC6_DoTicks(const int32_t ticks); -static inline void Wave_VRC6_DoTicks(const int32_t ticks) -#endif -{ - register int32_t i; - - for(i = 0; i < 2; i++) { - - if(mWave_VRC6Pulse[i].bChannelEnabled) { - - mWave_VRC6Pulse[i].nFreqCount -= ticks; - - if(mWave_VRC6Pulse[i].nDutyCount <= - mWave_VRC6Pulse[i].nDutyCycle) - { - mWave_VRC6Pulse[i].nMixL = - VRC6Pulse_nOutputTable_L[mWave_VRC6Pulse[i].nVolume]; - } - else - mWave_VRC6Pulse[i].nMixL = 0; - - while(mWave_VRC6Pulse[i].nFreqCount <= 0) { - mWave_VRC6Pulse[i].nFreqCount += - mWave_VRC6Pulse[i].nFreqTimer.W + 1; - - if(!mWave_VRC6Pulse[i].bDigitized) - mWave_VRC6Pulse[i].nDutyCount = - (mWave_VRC6Pulse[i].nDutyCount + 1) & 0x0F; - } - } - } - - if(mWave_VRC6Saw.bChannelEnabled) { - - mWave_VRC6Saw.nFreqCount -= ticks; - - mWave_VRC6Saw.nMixL = - VRC6Saw_nOutputTable_L[mWave_VRC6Saw.nAccum >> 3]; - - while(mWave_VRC6Saw.nFreqCount <= 0) { - - mWave_VRC6Saw.nFreqCount += mWave_VRC6Saw.nFreqTimer.W + 1; - - mWave_VRC6Saw.nAccumStep++; - if(mWave_VRC6Saw.nAccumStep == 14) - { - mWave_VRC6Saw.nAccumStep = 0; - mWave_VRC6Saw.nAccum = 0; - } - else if(!(mWave_VRC6Saw.nAccumStep & 1)) - mWave_VRC6Saw.nAccum += mWave_VRC6Saw.nAccumRate; - } - } -} - -/****************** Square waves ******************/ - -/* decay */ -#ifdef ICODE_INSTEAD_OF_INLINE -static void Wave_Squares_ClockMajor(void) ICODE_ATTR; -static void Wave_Squares_ClockMajor() -#else -static inline void Wave_Squares_ClockMajor(void); -static inline void Wave_Squares_ClockMajor() -#endif -{ - if(mWave_Squares.nDecayCount[0]) - mWave_Squares.nDecayCount[0]--; - else - { - mWave_Squares.nDecayCount[0] = mWave_Squares.nDecayTimer[0]; - if(mWave_Squares.nDecayVolume[0]) - mWave_Squares.nDecayVolume[0]--; - else - { - if(mWave_Squares.bDecayLoop[0]) - mWave_Squares.nDecayVolume[0] = 0x0F; - } - - if(mWave_Squares.bDecayEnable[0]) - mWave_Squares.nVolume[0] = mWave_Squares.nDecayVolume[0]; - } - - if(mWave_Squares.nDecayCount[1]) - mWave_Squares.nDecayCount[1]--; - else - { - mWave_Squares.nDecayCount[1] = mWave_Squares.nDecayTimer[1]; - if(mWave_Squares.nDecayVolume[1]) - mWave_Squares.nDecayVolume[1]--; - else - { - if(mWave_Squares.bDecayLoop[1]) - mWave_Squares.nDecayVolume[1] = 0x0F; - } - - if(mWave_Squares.bDecayEnable[1]) - mWave_Squares.nVolume[1] = mWave_Squares.nDecayVolume[1]; - } - -} - - -#ifdef ICODE_INSTEAD_OF_INLINE -static void Wave_Squares_CheckSweepForcedSilence(const int32_t i) ICODE_ATTR; -static void Wave_Squares_CheckSweepForcedSilence(const int32_t i) -#else -static inline void Wave_Squares_CheckSweepForcedSilence(const int32_t i); -static inline void Wave_Squares_CheckSweepForcedSilence(const int32_t i) -#endif -{ - if(mWave_Squares.nFreqTimer[i].W < 8) { - mWave_Squares.bSweepForceSilence[i] = 1; return; - } - if(!mWave_Squares.bSweepMode[i] && - (( mWave_Squares.nFreqTimer[i].W + - (mWave_Squares.nFreqTimer[i].W >> mWave_Squares.nSweepShift[i])) - >= 0x0800)) { mWave_Squares.bSweepForceSilence[i] = 1; return; } - - mWave_Squares.bSweepForceSilence[i] = 0; -} - -/* sweep / length */ -#ifdef ICODE_INSTEAD_OF_INLINE -static void Wave_Squares_ClockMinor(void) ICODE_ATTR; -static void Wave_Squares_ClockMinor() -#else -static inline void Wave_Squares_ClockMinor(void); -static inline void Wave_Squares_ClockMinor() -#endif -{ -/* unrolled a little loop - static int i = 0; - for(i = 0; i < 2; i++) - { -*/ - if(mWave_Squares.bLengthEnabled[0] && mWave_Squares.nLengthCount[0]) - mWave_Squares.nLengthCount[0]--; - - if(!mWave_Squares.bSweepEnable[0] || !mWave_Squares.nLengthCount[0] || - mWave_Squares.bSweepForceSilence[0] || !mWave_Squares.nSweepShift[0]) - goto other_square; - - if(mWave_Squares.nSweepCount[0]) - mWave_Squares.nSweepCount[0]--; - else - { - mWave_Squares.nSweepCount[0] = mWave_Squares.nSweepTimer[0]; - if(mWave_Squares.bSweepMode[0]) mWave_Squares.nFreqTimer[0].W -= - (mWave_Squares.nFreqTimer[0].W >> mWave_Squares.nSweepShift[0])+1; - else mWave_Squares.nFreqTimer[0].W += - (mWave_Squares.nFreqTimer[0].W >> mWave_Squares.nSweepShift[0]); - - Wave_Squares_CheckSweepForcedSilence(0); - } - - /* */ -other_square: - if(mWave_Squares.bLengthEnabled[1] && mWave_Squares.nLengthCount[1]) - mWave_Squares.nLengthCount[1]--; - - if(!mWave_Squares.bSweepEnable[1] || !mWave_Squares.nLengthCount[1] || - mWave_Squares.bSweepForceSilence[1] || !mWave_Squares.nSweepShift[1]) - return; - - if(mWave_Squares.nSweepCount[1]) - mWave_Squares.nSweepCount[1]--; - else - { - mWave_Squares.nSweepCount[1] = mWave_Squares.nSweepTimer[1]; - if(mWave_Squares.bSweepMode[1]) mWave_Squares.nFreqTimer[1].W -= - (mWave_Squares.nFreqTimer[1].W >> mWave_Squares.nSweepShift[1]); - else mWave_Squares.nFreqTimer[1].W += - (mWave_Squares.nFreqTimer[1].W >> mWave_Squares.nSweepShift[1]); - - Wave_Squares_CheckSweepForcedSilence(1); - } -} - -/****************** Triangle/noise/DMC ******************/ - -/* decay (noise), linear (tri) */ - -#ifdef ICODE_INSTEAD_OF_INLINE -static void Wave_TND_ClockMajor(void) ICODE_ATTR; -static void Wave_TND_ClockMajor() -#else -static inline void Wave_TND_ClockMajor(void); -static inline void Wave_TND_ClockMajor() -#endif -{ - /* noise's decay */ - if(mWave_TND.nNoiseDecayCount) - mWave_TND.nNoiseDecayCount--; - else - { - mWave_TND.nNoiseDecayCount = mWave_TND.nNoiseDecayTimer; - if(mWave_TND.nNoiseDecayVolume) - mWave_TND.nNoiseDecayVolume--; - else - { - if(mWave_TND.bNoiseDecayLoop) - mWave_TND.nNoiseDecayVolume = 0x0F; - } - - if(mWave_TND.bNoiseDecayEnable) - mWave_TND.nNoiseVolume = mWave_TND.nNoiseDecayVolume; - } - - /* triangle's linear */ - if(mWave_TND.bTriLinearHalt) - mWave_TND.nTriLinearCount = mWave_TND.nTriLinearLoad; - else if(mWave_TND.nTriLinearCount) - mWave_TND.nTriLinearCount--; - - if(!mWave_TND.bTriLinearControl) - mWave_TND.bTriLinearHalt = 0; -} - -/* length */ - -#ifdef ICODE_INSTEAD_OF_INLINE -static void Wave_TND_ClockMinor(void) ICODE_ATTR; -static void Wave_TND_ClockMinor() -#else -static inline void Wave_TND_ClockMinor(void); -static inline void Wave_TND_ClockMinor() -#endif -{ - if(mWave_TND.bNoiseLengthEnabled && mWave_TND.nNoiseLengthCount) - mWave_TND.nNoiseLengthCount--; - - if(mWave_TND.bTriLengthEnabled && mWave_TND.nTriLengthCount) - mWave_TND.nTriLengthCount--; -} - -/*#undef this*/ - -/****************** NSF Core ******************/ - -/* start globals */ - -/* - * Memory - */ -/* RAM: 0x0000 - 0x07FF */ -static uint8_t pRAM[0x800] IBSS_ATTR_NSF_LARGE_IRAM MEM_ALIGN_ATTR; -/* SRAM: 0x6000 - 0x7FFF (non-FDS only) */ -static uint8_t pSRAM[0x2000] IBSS_ATTR_NSF_LARGE_IRAM MEM_ALIGN_ATTR; -/* ExRAM: 0x5C00 - 0x5FF5 (MMC5 only) - * Also holds NSF player code (at 0x5000 - 0x500F) */ -static uint8_t pExRAM[0x1000] IBSS_ATTR_NSF_LARGE_IRAM MEM_ALIGN_ATTR; -/* Full ROM buffer */ -static uint8_t* pROM_Full IDATA_ATTR; - -static uint16_t main_nOutputTable_L[0x8000] MEM_ALIGN_ATTR; - -static uint8_t* pROM[10] IDATA_ATTR;/* ROM banks (point to areas in pROM_Full) */ - /* 0x8000 - 0xFFFF */ - /* also includes 0x6000 - 0x7FFF (FDS only) */ -static uint8_t* pStack; /* the stack (points to areas in pRAM) */ - /* 0x0100 - 0x01FF */ - -static int32_t nROMSize; /* size of this ROM file in bytes */ -static int32_t nROMBankCount; /* max number of 4k banks */ -static int32_t nROMMaxSize; /* size of allocated pROM_Full buffer */ - -/* - * Memory Proc Pointers - */ - -typedef uint8_t ( *ReadProc)(uint16_t); -typedef void ( *WriteProc)(uint16_t,uint8_t); -static ReadProc ReadMemory[0x10] IDATA_ATTR MEM_ALIGN_ATTR; -static WriteProc WriteMemory[0x10] IDATA_ATTR MEM_ALIGN_ATTR; - -/* - * 6502 Registers / Mode - */ - -static uint8_t regA IDATA_ATTR; /* Accumulator */ -static uint8_t regX IDATA_ATTR; /* X-Index */ -static uint8_t regY IDATA_ATTR; /* Y-Index */ -static uint8_t regP IDATA_ATTR; /* Processor Status */ -static uint8_t regSP IDATA_ATTR; /* Stack Pointer */ -static uint16_t regPC IDATA_ATTR; /* Program Counter */ - -static uint8_t bPALMode IDATA_ATTR;/* 1 if in PAL emulation mode, 0 if in NTSC */ -static uint8_t bCPUJammed IDATA_ATTR; /* 0 = not jammed. 1 = really - jammed. 2 = 'fake' jammed */ - /* fake jam caused by the NSF code to signal - * the end of the play/init routine */ - -/* Multiplication Register, for MMC5 chip only (5205+5206) */ -static uint8_t nMultIn_Low; -static uint8_t nMultIn_High; - -/* - * NSF Preparation Information - */ - -static uint8_t nBankswitchInitValues[10]; /* banks to swap to on tune init */ -static uint16_t nPlayAddress; /* Play routine address */ -static uint16_t nInitAddress; /* Init routine address */ - -static uint8_t nExternalSound; /* external sound chips */ -static uint8_t nCurTrack; - -static float fNSFPlaybackSpeed; - -/* - * pAPU - */ - -static uint8_t nFrameCounter; /* Frame Sequence Counter */ -static uint8_t nFrameCounterMax; /* Frame Sequence Counter Size - (3 or 4 depending on $4017.7) */ -static uint8_t bFrameIRQEnabled; /* TRUE if frame IRQs are enabled */ -static uint8_t bFrameIRQPending; /* TRUE if the frame sequencer is - holding down an IRQ */ - -static uint8_t nFME07_Address; - -/* - * Timing and Counters - */ -/* fixed point -15.16 */ - -static int32_t nTicksUntilNextFrame; -static int32_t nTicksPerPlay; -static int32_t nTicksUntilNextPlay; -static int32_t nTicksPerSample; -static int32_t nTicksUntilNextSample; - -static uint32_t nCPUCycle IDATA_ATTR; -static uint32_t nAPUCycle IDATA_ATTR; - - -static uint32_t nTotalPlays; /* number of times the play subroutine has been - called (for tracking output time) */ -/* - * Silence Tracker - */ -static int32_t nSilentSamples; -static int32_t nSilentSampleMax; -static int32_t nSilenceTrackMS; -static uint8_t bNoSilenceIfTime; -static uint8_t bTimeNotDefault; - -/* - * Sound output options - */ -static const int32_t nSampleRate=44100; - -/* - * Volume/fading/filter tracking - */ - -static uint32_t nStartFade; /* play call to start fading out */ -static uint32_t nEndFade; /* play call to stop fading out (song is over) */ -static uint8_t bFade; /* are we fading? */ -static float fFadeVolume; -static float fFadeChange; - -/* - * Designated Output Buffer - */ -static uint8_t* pOutput IDATA_ATTR; - -static const uint8_t bDMCPopReducer=1; -static uint8_t nDMCPop_Prev IDATA_ATTR = 0; -static uint8_t bDMCPop_Skip IDATA_ATTR = 0; -static uint8_t bDMCPop_SamePlay IDATA_ATTR = 0; - -static const uint8_t nForce4017Write=0; -static const uint8_t bN106PopReducer=0; -static const uint8_t bIgnore4011Writes=0; - -static const uint8_t bIgnoreBRK=0; -static const uint8_t bIgnoreIllegalOps=0; -static const uint8_t bNoWaitForReturn=0; -static const uint8_t bPALPreference=0; -static const uint8_t bCleanAXY=0; -static const uint8_t bResetDuty=0; - -/* - * Sound Filter - */ - -static int64_t nFilterAccL IDATA_ATTR; -static int64_t nHighPass IDATA_ATTR; - -static int32_t nHighPassBase IDATA_ATTR; - -static uint8_t bHighPassEnabled IDATA_ATTR; - -/* end globals */ - -#define CLOCK_MAJOR() { Wave_Squares_ClockMajor(); Wave_TND_ClockMajor(); } -#define CLOCK_MINOR() { Wave_Squares_ClockMinor(); Wave_TND_ClockMinor(); } - -#define EXTSOUND_VRC6 0x01 -#define EXTSOUND_VRC7 0x02 -#define EXTSOUND_FDS 0x04 -#define EXTSOUND_MMC5 0x08 -#define EXTSOUND_N106 0x10 -#define EXTSOUND_FME07 0x20 - -#define SILENCE_THRESHOLD 3 - -/* - * prototypes - */ - -static uint32_t Emulate6502(uint32_t runto) ICODE_ATTR; -static void EmulateAPU(uint8_t bBurnCPUCycles) ICODE_ATTR; - -static int NSFCore_Initialize(void); /* 1 = initialized ok, - 0 = couldn't initialize (memory allocation error) */ - -/* - * Song Loading - */ -static int LoadNSF(int32_t); /* grab data from an existing file - 1 = loaded ok, 0 = error loading */ - -/* - * Track Control - */ -static void SetTrack(uint8_t track); /* Change tracks */ - -/* - * Getting Samples - */ -/* fill a buffer with samples */ -static int32_t GetSamples(uint8_t* buffer, int32_t buffersize); - -/* - * Playback options - */ -/* Set desired playback options (0 = bad options couldn't be set) */ -static int SetPlaybackOptions(int32_t samplerate); -/* Speed throttling (0 = uses NSF specified speed) */ -static void SetPlaybackSpeed(float playspersec); - -static float GetPlaybackSpeed(void); -/* rockbox: not used -float GetMasterVolume(void); */ - -/* rockbox: not used */ -#if 0 -/* - * Seeking - */ -/* gets the number of 'play' routine calls executed */ -float GetPlayCalls(void); - -/* gets the output time (based on the given play rate, - if basedplayspersec is zero, current playback speed is used */ -uint32_t GetWrittenTime(float basedplayspersec); -/* sets the number of 'plays' routines executed (for precise seeking) */ -void SetPlayCalls(float plays); -/* sets the written time (approx. seeking) */ -void SetWrittenTime(uint32_t ms,float basedplays); -#endif - -/* - * Fading - */ - -/* rockbox: not used -void StopFade(void); */ /* stops all fading (plays indefinitely) */ -static uint8_t SongCompleted(void); /* song has faded out (samples have - stopped being generated) */ -/* parameters are play calls */ -static void SetFade(int32_t fadestart,int32_t fadestop,uint8_t bNotDefault); -static void SetFadeTime(uint32_t fadestart,uint32_t fadestop, - float basedplays, uint8_t bNotDefault); - /* parameters are in milliseconds */ - -/* - * Internal Functions - */ -static void RebuildOutputTables(void); -static void RecalculateFade(void); /* called when fade status is changed. */ -static void RecalcFilter(void); -static void RecalcSilenceTracker(void); - -static void WriteMemory_VRC6(uint16_t a,uint8_t v) ICODE_ATTR; -static void WriteMemory_MMC5(uint16_t a,uint8_t v) ICODE_ATTR; -static void WriteMemory_N106(uint16_t a,uint8_t v) ICODE_ATTR; -static void WriteMemory_FME07(uint16_t a,uint8_t v) ICODE_ATTR; - -/* - * Memory Read/Write routines - */ - -static uint8_t ReadMemory_RAM(uint16_t a) ICODE_ATTR; -static uint8_t ReadMemory_ExRAM(uint16_t a) ICODE_ATTR; -static uint8_t ReadMemory_SRAM(uint16_t a) ICODE_ATTR; -static uint8_t ReadMemory_pAPU(uint16_t a) ICODE_ATTR; -static uint8_t ReadMemory_ROM(uint16_t a) ICODE_ATTR; -static uint8_t ReadMemory_Default(uint16_t a) ICODE_ATTR; - -static uint8_t ReadMemory_N106(uint16_t a) ICODE_ATTR; - -static void WriteMemory_RAM(uint16_t a,uint8_t v) ICODE_ATTR; -static void WriteMemory_ExRAM(uint16_t a,uint8_t v) ICODE_ATTR; -static void WriteMemory_SRAM(uint16_t a,uint8_t v) ICODE_ATTR; -static void WriteMemory_pAPU(uint16_t a,uint8_t v) ICODE_ATTR; -static void WriteMemory_FDSRAM(uint16_t a,uint8_t v) ICODE_ATTR; -static void WriteMemory_Default(uint16_t a,uint8_t v) ICODE_ATTR; - -static uint8_t ReadMemory_RAM(uint16_t a) { return pRAM[a & 0x07FF]; } -static uint8_t ReadMemory_ExRAM(uint16_t a) { return pExRAM[a & 0x0FFF]; } -static uint8_t ReadMemory_SRAM(uint16_t a) { return pSRAM[a & 0x1FFF]; } -static uint8_t ReadMemory_ROM(uint16_t a) - { return pROM[(a >> 12) - 6][a & 0x0FFF]; } -static uint8_t ReadMemory_Default(uint16_t a) { return (a >> 8); } - -static void WriteMemory_RAM(uint16_t a,uint8_t v) - { pRAM[a & 0x07FF] = v; } -static void WriteMemory_ExRAM(uint16_t a,uint8_t v); -static void WriteMemory_SRAM(uint16_t a,uint8_t v) - { pSRAM[a & 0x1FFF] = v; } -static void WriteMemory_FDSRAM(uint16_t a,uint8_t v) - { pROM[(a >> 12) - 6][a & 0x0FFF] = v; } -static void WriteMemory_Default(uint16_t a,uint8_t v) { (void)a; (void)v; } - - -/* Read Memory Procs */ - -static uint8_t ReadMemory_pAPU(uint16_t a) -{ - EmulateAPU(1); - - if(a == 0x4015) - { - uint8_t ret = 0; - if(mWave_Squares.nLengthCount[0]) ret |= 0x01; - if(mWave_Squares.nLengthCount[1]) ret |= 0x02; - if(mWave_TND.nTriLengthCount) ret |= 0x04; - if(mWave_TND.nNoiseLengthCount) ret |= 0x08; - if(mWave_TND.nDMCBytesRemaining) ret |= 0x10; - - if(bFrameIRQPending) ret |= 0x40; - if(mWave_TND.bDMCIRQPending) ret |= 0x80; - - bFrameIRQPending = 0; - return ret; - } - - if(!(nExternalSound & EXTSOUND_FDS)) return 0x40; - if(bPALMode) return 0x40; - - if((a >= 0x4040) && (a <= 0x407F)) - return mWave_FDS.nWaveTable[a & 0x3F] | 0x40; - if(a == 0x4090) - return (mWave_FDS.nVolEnv_Gain & 0x3F) | 0x40; - if(a == 0x4092) - return (mWave_FDS.nSweep_Gain & 0x3F) | 0x40; - - return 0x40; -} - -static uint8_t ReadMemory_N106(uint16_t a) -{ - if(a != 0x4800) - return ReadMemory_pAPU(a); - - uint8_t ret = mWave_N106.nRAM[(mWave_N106.nCurrentAddress << 1)] | - (mWave_N106.nRAM[(mWave_N106.nCurrentAddress << 1) + 1] << 4); - if(mWave_N106.bAutoIncrement) - mWave_N106.nCurrentAddress = (mWave_N106.nCurrentAddress + 1) & 0x7F; - - return ret; -} - - -/* Write Memory Procs */ - -static void WriteMemory_ExRAM(uint16_t a,uint8_t v) -{ - if(a < 0x5FF6) /* Invalid */ - return; - - a -= 0x5FF6; - - /* Swap out banks */ - - EmulateAPU(1); - /* stop it from swapping to a bank that doesn't exist */ - if(v >= nROMBankCount) - v = 0; - - pROM[a] = pROM_Full + (v << 12); - - /* Update the DMC's DMA pointer, as well */ - if(a >= 2) - mWave_TND.pDMCDMAPtr[a - 2] = pROM[a]; -} - -static void WriteMemory_pAPU(uint16_t a,uint8_t v) -{ - EmulateAPU(1); - switch(a) - { - /* Square 1 */ - case 0x4000: - mWave_Squares.nDutyCycle[0] = DUTY_CYCLE_TABLE[v >> 6]; - mWave_Squares.bLengthEnabled[0] = - !(mWave_Squares.bDecayLoop[0] = (v & 0x20)); - mWave_Squares.bDecayEnable[0] = !(v & 0x10); - mWave_Squares.nDecayTimer[0] = (v & 0x0F); - - if(!mWave_Squares.bDecayEnable[0]) - mWave_Squares.nVolume[0] = mWave_Squares.nDecayTimer[0]; - break; - - case 0x4001: - mWave_Squares.bSweepEnable[0] = (v & 0x80); - mWave_Squares.nSweepTimer[0] = (v & 0x70) >> 4; - mWave_Squares.bSweepMode[0] = v & 0x08; - mWave_Squares.nSweepShift[0] = v & 0x07; - Wave_Squares_CheckSweepForcedSilence(0); - break; - - case 0x4002: - mWave_Squares.nFreqTimer[0].B.l = v; - Wave_Squares_CheckSweepForcedSilence(0); - break; - - case 0x4003: - mWave_Squares.nFreqTimer[0].B.h = v & 0x07; - Wave_Squares_CheckSweepForcedSilence(0); - - mWave_Squares.nDecayVolume[0] = 0x0F; - - if(mWave_Squares.bChannelEnabled[0]) - mWave_Squares.nLengthCount[0] = LENGTH_COUNTER_TABLE[v >> 3]; - - if(bResetDuty) - mWave_Squares.nDutyCount[0] = 0; - break; - - - /* Square 2 */ - case 0x4004: - mWave_Squares.nDutyCycle[1] = DUTY_CYCLE_TABLE[v >> 6]; - mWave_Squares.bLengthEnabled[1] = - !(mWave_Squares.bDecayLoop[1] = (v & 0x20)); - mWave_Squares.bDecayEnable[1] = !(v & 0x10); - mWave_Squares.nDecayTimer[1] = (v & 0x0F); - - if(!mWave_Squares.bDecayEnable[1]) - mWave_Squares.nVolume[1] = mWave_Squares.nDecayTimer[1]; - break; - - case 0x4005: - mWave_Squares.bSweepEnable[1] = (v & 0x80); - mWave_Squares.nSweepTimer[1] = (v & 0x70) >> 4; - mWave_Squares.bSweepMode[1] = v & 0x08; - mWave_Squares.nSweepShift[1] = v & 0x07; - Wave_Squares_CheckSweepForcedSilence(1); - break; - - case 0x4006: - mWave_Squares.nFreqTimer[1].B.l = v; - Wave_Squares_CheckSweepForcedSilence(1); - break; - - case 0x4007: - mWave_Squares.nFreqTimer[1].B.h = v & 0x07; - Wave_Squares_CheckSweepForcedSilence(1); - - mWave_Squares.nDecayVolume[1] = 0x0F; - - if(mWave_Squares.bChannelEnabled[1]) - mWave_Squares.nLengthCount[1] = LENGTH_COUNTER_TABLE[v >> 3]; - - if(bResetDuty) - mWave_Squares.nDutyCount[1] = 0; - break; - - - /* Triangle */ - case 0x4008: - mWave_TND.nTriLinearLoad = v & 0x7F; - mWave_TND.bTriLinearControl = v & 0x80; - mWave_TND.bTriLengthEnabled = !(v & 0x80); - break; - - case 0x400A: - mWave_TND.nTriFreqTimer.B.l = v; - break; - - case 0x400B: - mWave_TND.nTriFreqTimer.B.h = v & 0x07; - mWave_TND.bTriLinearHalt = 1; - - if(mWave_TND.bTriChannelEnabled) - mWave_TND.nTriLengthCount = LENGTH_COUNTER_TABLE[v >> 3]; - break; - - /* Noise */ - case 0x400C: - mWave_TND.bNoiseLengthEnabled = - !(mWave_TND.bNoiseDecayLoop = (v & 0x20)); - mWave_TND.bNoiseDecayEnable = !(v & 0x10); - mWave_TND.nNoiseDecayTimer = (v & 0x0F); - - if(mWave_TND.bNoiseDecayEnable) - mWave_TND.nNoiseVolume = mWave_TND.nNoiseDecayVolume; - else - mWave_TND.nNoiseVolume = mWave_TND.nNoiseDecayTimer; - break; - - case 0x400E: - mWave_TND.nNoiseFreqTimer = NOISE_FREQ_TABLE[v & 0x0F]; - mWave_TND.bNoiseRandomMode = (v & 0x80) ? 6 : 1; - break; - - case 0x400F: - if(mWave_TND.bNoiseChannelEnabled) - mWave_TND.nNoiseLengthCount = LENGTH_COUNTER_TABLE[v >> 3]; - - mWave_TND.nNoiseDecayVolume = 0x0F; - if(mWave_TND.bNoiseDecayEnable) - mWave_TND.nNoiseVolume = 0x0F; - break; - - /* DMC */ - case 0x4010: - mWave_TND.bDMCLoop = v & 0x40; - mWave_TND.bDMCIRQEnabled = v & 0x80; - /* IRQ can't be pending if disabled */ - if(!mWave_TND.bDMCIRQEnabled) - mWave_TND.bDMCIRQPending = 0; - - mWave_TND.nDMCFreqTimer = DMC_FREQ_TABLE[bPALMode][v & 0x0F]; - break; - - case 0x4011: - if(bIgnore4011Writes) - break; - v &= 0x7F; - if(bDMCPopReducer) - { - if(bDMCPop_SamePlay) - mWave_TND.nDMCOutput = v; - else - { - if(bDMCPop_Skip) - { - bDMCPop_Skip = 0; - break; - } - if(nDMCPop_Prev == v) break; - if(mWave_TND.nDMCOutput == v) break; - mWave_TND.nDMCOutput = nDMCPop_Prev; - nDMCPop_Prev = v; - bDMCPop_SamePlay = 1; - } - } - else - mWave_TND.nDMCOutput = v; - break; - - case 0x4012: - mWave_TND.nDMCDMABank_Load = (v >> 6) | 0x04; - mWave_TND.nDMCDMAAddr_Load = (v << 6) & 0x0FFF; - break; - - case 0x4013: - mWave_TND.nDMCLength = (v << 4) + 1; - break; - - /* All / General Purpose */ - case 0x4015: - mWave_TND.bDMCIRQPending = 0; - - if(v & 0x01){ mWave_Squares.bChannelEnabled[0] = 1; } - else { mWave_Squares.bChannelEnabled[0] = - mWave_Squares.nLengthCount[0] = 0; } - if(v & 0x02){ mWave_Squares.bChannelEnabled[1] = 1; } - else { mWave_Squares.bChannelEnabled[1] = - mWave_Squares.nLengthCount[1] = 0; } - if(v & 0x04){ mWave_TND.bTriChannelEnabled = 1; } - else { mWave_TND.bTriChannelEnabled = - mWave_TND.nTriLengthCount = 0; } - if(v & 0x08){ mWave_TND.bNoiseChannelEnabled = 1; } - else { mWave_TND.bNoiseChannelEnabled = - mWave_TND.nNoiseLengthCount = 0; } - - if(v & 0x10) - { - if(!mWave_TND.nDMCBytesRemaining) - { - bDMCPop_Skip = 1; - mWave_TND.nDMCDMAAddr = mWave_TND.nDMCDMAAddr_Load; - mWave_TND.nDMCDMABank = mWave_TND.nDMCDMABank_Load; - mWave_TND.nDMCBytesRemaining = mWave_TND.nDMCLength; - mWave_TND.bDMCActive = 1; - } - } - else - mWave_TND.nDMCBytesRemaining = 0; - break; - - case 0x4017: - bFrameIRQEnabled = !(v & 0x40); - bFrameIRQPending = 0; - nFrameCounter = 0; - nFrameCounterMax = (v & 0x80) ? 4 : 3; - nTicksUntilNextFrame = - (bPALMode ? PAL_FRAME_COUNTER_FREQ : NTSC_FRAME_COUNTER_FREQ) - * 0x10000; - - CLOCK_MAJOR(); - if(v & 0x80) CLOCK_MINOR(); - break; - } - - if(!(nExternalSound & EXTSOUND_FDS)) return; - if(bPALMode) return; - - /* FDS Sound registers */ - - if(a < 0x4040) return; - - /* wave table */ - if(a <= 0x407F) - { - if(mWave_FDS.bWaveWrite) - mWave_FDS.nWaveTable[a - 0x4040] = v; - } - else - { - switch(a) - { - case 0x4080: - mWave_FDS.nVolEnv_Mode = (v >> 6); - if(v & 0x80) - { - mWave_FDS.nVolEnv_Gain = v & 0x3F; - if(!mWave_FDS.nMainAddr) - { - if(mWave_FDS.nVolEnv_Gain < 0x20) - mWave_FDS.nVolume = mWave_FDS.nVolEnv_Gain; - else mWave_FDS.nVolume = 0x20; - } - } - mWave_FDS.nVolEnv_Decay = v & 0x3F; - mWave_FDS.nVolEnv_Timer = - ((mWave_FDS.nVolEnv_Decay + 1) * mWave_FDS.nEnvelopeSpeed * 8); - - mWave_FDS.bVolEnv_On = mWave_FDS.bEnvelopeEnable && - mWave_FDS.nEnvelopeSpeed && !(v & 0x80); - break; - - case 0x4082: - mWave_FDS.nFreq.B.l = v; - mWave_FDS.bMain_On = mWave_FDS.nFreq.W && mWave_FDS.bEnabled && - !mWave_FDS.bWaveWrite; - break; - - case 0x4083: - mWave_FDS.bEnabled = !(v & 0x80); - mWave_FDS.bEnvelopeEnable = !(v & 0x40); - if(v & 0x80) - { - if(mWave_FDS.nVolEnv_Gain < 0x20) - mWave_FDS.nVolume = mWave_FDS.nVolEnv_Gain; - else mWave_FDS.nVolume = 0x20; - } - mWave_FDS.nFreq.B.h = v & 0x0F; - mWave_FDS.bMain_On = mWave_FDS.nFreq.W && mWave_FDS.bEnabled && - !mWave_FDS.bWaveWrite; - - mWave_FDS.bVolEnv_On = mWave_FDS.bEnvelopeEnable && - mWave_FDS.nEnvelopeSpeed && !(mWave_FDS.nVolEnv_Mode & 2); - mWave_FDS.bSweepEnv_On = mWave_FDS.bEnvelopeEnable && - mWave_FDS.nEnvelopeSpeed && !(mWave_FDS.nSweep_Mode & 2); - break; - - - case 0x4084: - mWave_FDS.nSweep_Mode = v >> 6; - if(v & 0x80) - mWave_FDS.nSweep_Gain = v & 0x3F; - mWave_FDS.nSweep_Decay = v & 0x3F; - mWave_FDS.nSweep_Timer = - ((mWave_FDS.nSweep_Decay + 1) * mWave_FDS.nEnvelopeSpeed * 8); - mWave_FDS.bSweepEnv_On = - mWave_FDS.bEnvelopeEnable && mWave_FDS.nEnvelopeSpeed && - !(v & 0x80); - break; - - - case 0x4085: - if(v & 0x40) mWave_FDS.nSweepBias = (v & 0x3F) - 0x40; - else mWave_FDS.nSweepBias = v & 0x3F; - mWave_FDS.nLFO_Addr = 0; - break; - - - case 0x4086: - mWave_FDS.nLFO_Freq.B.l = v; - mWave_FDS.bLFO_On = - mWave_FDS.bLFO_Enabled && mWave_FDS.nLFO_Freq.W; - if(mWave_FDS.nLFO_Freq.W) - mWave_FDS.nLFO_Timer = (0x10000<<14) / mWave_FDS.nLFO_Freq.W; - break; - - case 0x4087: - mWave_FDS.bLFO_Enabled = !(v & 0x80); - mWave_FDS.nLFO_Freq.B.h = v & 0x0F; - mWave_FDS.bLFO_On = - mWave_FDS.bLFO_Enabled && mWave_FDS.nLFO_Freq.W; - if(mWave_FDS.nLFO_Freq.W) - mWave_FDS.nLFO_Timer = (0x10000<<14) / mWave_FDS.nLFO_Freq.W; - break; - - case 0x4088: - if(mWave_FDS.bLFO_Enabled) break; - register int32_t i; - for(i = 0; i < 62; i++) - mWave_FDS.nLFO_Table[i] = mWave_FDS.nLFO_Table[i + 2]; - mWave_FDS.nLFO_Table[62] = mWave_FDS.nLFO_Table[63] = v & 7; - break; - - case 0x4089: - mWave_FDS.nMainVolume = v & 3; - mWave_FDS.bWaveWrite = v & 0x80; - mWave_FDS.bMain_On = mWave_FDS.nFreq.W && mWave_FDS.bEnabled && - !mWave_FDS.bWaveWrite; - break; - - case 0x408A: - mWave_FDS.nEnvelopeSpeed = v; - mWave_FDS.bVolEnv_On = - mWave_FDS.bEnvelopeEnable && - mWave_FDS.nEnvelopeSpeed && !(mWave_FDS.nVolEnv_Mode & 2); - mWave_FDS.bSweepEnv_On = - mWave_FDS.bEnvelopeEnable && - mWave_FDS.nEnvelopeSpeed && !(mWave_FDS.nSweep_Mode & 2); - break; - } - } -} - -static void WriteMemory_VRC6(uint16_t a,uint8_t v) -{ - EmulateAPU(1); - - if((a < 0xA000) && (nExternalSound & EXTSOUND_VRC7)) return; - else if(nExternalSound & EXTSOUND_FDS) - WriteMemory_FDSRAM(a,v); - - switch(a) - { - /* Pulse 1 */ - case 0x9000: - mWave_VRC6Pulse[0].nVolume = v & 0x0F; - mWave_VRC6Pulse[0].nDutyCycle = (v >> 4) & 0x07; - mWave_VRC6Pulse[0].bDigitized = v & 0x80; - if(mWave_VRC6Pulse[0].bDigitized) - mWave_VRC6Pulse[0].nDutyCount = 0; - break; - - case 0x9001: - mWave_VRC6Pulse[0].nFreqTimer.B.l = v; - break; - - case 0x9002: - mWave_VRC6Pulse[0].nFreqTimer.B.h = v & 0x0F; - mWave_VRC6Pulse[0].bChannelEnabled = v & 0x80; - break; - - - /* Pulse 2 */ - case 0xA000: - mWave_VRC6Pulse[1].nVolume = v & 0x0F; - mWave_VRC6Pulse[1].nDutyCycle = (v >> 4) & 0x07; - mWave_VRC6Pulse[1].bDigitized = v & 0x80; - if(mWave_VRC6Pulse[1].bDigitized) - mWave_VRC6Pulse[1].nDutyCount = 0; - break; - - case 0xA001: - mWave_VRC6Pulse[1].nFreqTimer.B.l = v; - break; - - case 0xA002: - mWave_VRC6Pulse[1].nFreqTimer.B.h = v & 0x0F; - mWave_VRC6Pulse[1].bChannelEnabled = v & 0x80; - break; - - /* Sawtooth */ - case 0xB000: - mWave_VRC6Saw.nAccumRate = (v & 0x3F); - break; - - case 0xB001: - mWave_VRC6Saw.nFreqTimer.B.l = v; - break; - - case 0xB002: - mWave_VRC6Saw.nFreqTimer.B.h = v & 0x0F; - mWave_VRC6Saw.bChannelEnabled = v & 0x80; - break; - } -} - -static void WriteMemory_MMC5(uint16_t a,uint8_t v) -{ - if((a <= 0x5015) && !bPALMode) - { - /* no audio emulation */ - return; - } - - if(a == 0x5205) - { - nMultIn_Low = v; - goto multiply; - } - if(a == 0x5206) - { - nMultIn_High = v; -multiply: - a = nMultIn_Low * nMultIn_High; - pExRAM[0x205] = a & 0xFF; - pExRAM[0x206] = a >> 8; - return; - } - - if(a < 0x5C00) return; - - pExRAM[a & 0x0FFF] = v; - if(a >= 0x5FF6) - WriteMemory_ExRAM(a,v); -} - -static void WriteMemory_N106(uint16_t a,uint8_t v) -{ - if(a < 0x4800) - { - WriteMemory_pAPU(a,v); - return; - } - - if(a == 0xF800) - { - mWave_N106.nCurrentAddress = v & 0x7F; - mWave_N106.bAutoIncrement = (v & 0x80); - return; - } - - if(a == 0x4800) - { - EmulateAPU(1); - mWave_N106.nRAM[mWave_N106.nCurrentAddress << 1] = v & 0x0F; - mWave_N106.nRAM[(mWave_N106.nCurrentAddress << 1) + 1] = v >> 4; - a = mWave_N106.nCurrentAddress; - if(mWave_N106.bAutoIncrement) - mWave_N106.nCurrentAddress = - (mWave_N106.nCurrentAddress + 1) & 0x7F; - -#define N106REGWRITE(ch,r0,r1,r2,r3,r4) \ - case r0: if(mWave_N106.nFreqReg[ch].B.l == v) break; \ - mWave_N106.nFreqReg[ch].B.l = v; \ - mWave_N106.nFreqTimer[ch] = -1; \ - break; \ - case r1: if(mWave_N106.nFreqReg[ch].B.h == v) break; \ - mWave_N106.nFreqReg[ch].B.h = v; \ - mWave_N106.nFreqTimer[ch] = -1; \ - break; \ - case r2: if(mWave_N106.nFreqReg[ch].B.w != (v & 3)){ \ - mWave_N106.nFreqReg[ch].B.w = v & 0x03; \ - mWave_N106.nFreqTimer[ch] = -1;} \ - mWave_N106.nWaveSize[ch] = 0x20 - (v & 0x1C); \ - break; \ - case r3: mWave_N106.nWavePosStart[ch] = v; \ - break; \ - case r4: mWave_N106.nPreVolume[ch] = v & 0x0F; \ - if(!bN106PopReducer) \ - mWave_N106.nVolume[ch] = v & 0x0F - - switch(a) - { - N106REGWRITE(0,0x40,0x42,0x44,0x46,0x47); break; - N106REGWRITE(1,0x48,0x4A,0x4C,0x4E,0x4F); break; - N106REGWRITE(2,0x50,0x52,0x54,0x56,0x57); break; - N106REGWRITE(3,0x58,0x5A,0x5C,0x5E,0x5F); break; - N106REGWRITE(4,0x60,0x62,0x64,0x66,0x67); break; - N106REGWRITE(5,0x68,0x6A,0x6C,0x6E,0x6F); break; - N106REGWRITE(6,0x70,0x72,0x74,0x76,0x77); break; - N106REGWRITE(7,0x78,0x7A,0x7C,0x7E,0x7F); - v = (v >> 4) & 7; - if(mWave_N106.nActiveChannels == v) break; - mWave_N106.nActiveChannels = v; - mWave_N106.nFreqTimer[0] = -1; - mWave_N106.nFreqTimer[1] = -1; - mWave_N106.nFreqTimer[2] = -1; - mWave_N106.nFreqTimer[3] = -1; - mWave_N106.nFreqTimer[4] = -1; - mWave_N106.nFreqTimer[5] = -1; - mWave_N106.nFreqTimer[6] = -1; - mWave_N106.nFreqTimer[7] = -1; - break; - } -#undef N106REGWRITE - } -} - -static void WriteMemory_FME07(uint16_t a,uint8_t v) -{ - if((a < 0xD000) && (nExternalSound & EXTSOUND_FDS)) - WriteMemory_FDSRAM(a,v); - - if(a == 0xC000) - nFME07_Address = v; - if(a == 0xE000) - { - switch(nFME07_Address) - { - case 0x00: mWave_FME07[0].nFreqTimer.B.l = v; break; - case 0x01: mWave_FME07[0].nFreqTimer.B.h = v & 0x0F; break; - case 0x02: mWave_FME07[1].nFreqTimer.B.l = v; break; - case 0x03: mWave_FME07[1].nFreqTimer.B.h = v & 0x0F; break; - case 0x04: mWave_FME07[2].nFreqTimer.B.l = v; break; - case 0x05: mWave_FME07[2].nFreqTimer.B.h = v & 0x0F; break; - case 0x07: - mWave_FME07[0].bChannelEnabled = !(v & 0x01); - mWave_FME07[1].bChannelEnabled = !(v & 0x02); - mWave_FME07[2].bChannelEnabled = !(v & 0x03); - break; - case 0x08: mWave_FME07[0].nVolume = v & 0x0F; break; - case 0x09: mWave_FME07[1].nVolume = v & 0x0F; break; - case 0x0A: mWave_FME07[2].nVolume = v & 0x0F; break; - } - } -} - -/* - * Emulate APU - */ - -static int32_t fulltick; -static void EmulateAPU(uint8_t bBurnCPUCycles) -{ - int32_t tick; - int64_t diff; - - int32_t tnd_out; - int square_out1; - int square_out2; - - ENTER_TIMER(apu); - - fulltick += (signed)(nCPUCycle - nAPUCycle); - - int32_t burned; - int32_t mixL; - - if(bFade && nSilentSampleMax && (nSilentSamples >= nSilentSampleMax)) - fulltick = 0; - - while(fulltick>0) - { - tick = (nTicksUntilNextSample+0xffff)>>16; - - fulltick -= tick; - - /* - * Sample Generation - */ - - ENTER_TIMER(squares); - /* Square generation */ - - mWave_Squares.nFreqCount[0] -= tick; - mWave_Squares.nFreqCount[1] -= tick; - - if((mWave_Squares.nDutyCount[0] < mWave_Squares.nDutyCycle[0]) && - mWave_Squares.nLengthCount[0] && - !mWave_Squares.bSweepForceSilence[0]) - square_out1 = mWave_Squares.nVolume[0]; - else - square_out1 = 0; - - if((mWave_Squares.nDutyCount[1] < mWave_Squares.nDutyCycle[1]) && - mWave_Squares.nLengthCount[1] && - !mWave_Squares.bSweepForceSilence[1]) - square_out2 = mWave_Squares.nVolume[1]; - else - square_out2 = 0; - - mWave_Squares.nMixL = Squares_nOutputTable_L[square_out1][square_out2]; - - if(mWave_Squares.nFreqCount[0]<=0) - { - int cycles = - (-mWave_Squares.nFreqCount[0])/ - (mWave_Squares.nFreqTimer[0].W + 1) + 1; - mWave_Squares.nFreqCount[0] = - (mWave_Squares.nFreqTimer[0].W + 1)- - (-mWave_Squares.nFreqCount[0])% - (mWave_Squares.nFreqTimer[0].W + 1); - mWave_Squares.nDutyCount[0] = - (mWave_Squares.nDutyCount[0]+cycles)%0x10; - } - if(mWave_Squares.nFreqCount[1]<=0) - { - int cycles = - (-mWave_Squares.nFreqCount[1])/ - (mWave_Squares.nFreqTimer[1].W + 1) + 1; - mWave_Squares.nFreqCount[1] = - (mWave_Squares.nFreqTimer[1].W + 1)- - (-mWave_Squares.nFreqCount[1])% - (mWave_Squares.nFreqTimer[1].W + 1); - mWave_Squares.nDutyCount[1] = (mWave_Squares.nDutyCount[1]+cycles)% - 0x10; - } - /* end of Square generation */ - EXIT_TIMER(squares); - ENTER_TIMER(tnd); - - ENTER_TIMER(tnd_enter); - - burned=0; - - /* TND generation */ - - if(mWave_TND.nNoiseFreqTimer) mWave_TND.nNoiseFreqCount -= tick; - - if(mWave_TND.nTriFreqTimer.W > 8) - mWave_TND.nTriFreqCount -= tick; - - tnd_out = mWave_TND.nTriOutput << 11; - - if(mWave_TND.bNoiseRandomOut && mWave_TND.nNoiseLengthCount) - tnd_out |= mWave_TND.nNoiseVolume << 7; - - tnd_out |= mWave_TND.nDMCOutput; - - mWave_TND.nMixL = main_nOutputTable_L[tnd_out]; - - EXIT_TIMER(tnd_enter); - - ENTER_TIMER(tnd_tri); - - /* Tri */ - - if(mWave_TND.nTriFreqCount<=0) - { - if(mWave_TND.nTriLengthCount && mWave_TND.nTriLinearCount) - { - do mWave_TND.nTriStep++; - while ((mWave_TND.nTriFreqCount += - mWave_TND.nTriFreqTimer.W + 1) <= 0); - mWave_TND.nTriStep &= 0x1F; - - if(mWave_TND.nTriStep & 0x10) - mWave_TND.nTriOutput = mWave_TND.nTriStep ^ 0x1F; - else mWave_TND.nTriOutput = mWave_TND.nTriStep; - } else mWave_TND.nTriFreqCount=mWave_TND.nTriFreqTimer.W+1; - } - - EXIT_TIMER(tnd_tri); - - ENTER_TIMER(tnd_noise); - - /* Noise */ - - if(mWave_TND.nNoiseFreqTimer && - mWave_TND.nNoiseVolume && mWave_TND.nNoiseFreqCount<=0) - { - mWave_TND.nNoiseFreqCount = mWave_TND.nNoiseFreqTimer; - mWave_TND.nNoiseRandomShift <<= 1; - mWave_TND.bNoiseRandomOut = (((mWave_TND.nNoiseRandomShift << - mWave_TND.bNoiseRandomMode) ^ - mWave_TND.nNoiseRandomShift) & 0x8000 ) ? 1 : 0; - if(mWave_TND.bNoiseRandomOut) - mWave_TND.nNoiseRandomShift |= 0x01; - } - - EXIT_TIMER(tnd_noise); - - ENTER_TIMER(tnd_dmc); - - /* DMC */ - if(mWave_TND.bDMCActive) - { - mWave_TND.nDMCFreqCount -= tick; - while (mWave_TND.nDMCFreqCount <= 0) { - if (!mWave_TND.bDMCActive) { - mWave_TND.nDMCFreqCount = mWave_TND.nDMCFreqTimer; - break; - } - - mWave_TND.nDMCFreqCount += mWave_TND.nDMCFreqTimer; - - if(mWave_TND.bDMCSampleBufferEmpty && - mWave_TND.nDMCBytesRemaining) - { - burned += 4; /* 4 cycle burn! */ - mWave_TND.nDMCSampleBuffer = - mWave_TND.pDMCDMAPtr[mWave_TND.nDMCDMABank] - [mWave_TND.nDMCDMAAddr]; - mWave_TND.nDMCDMAAddr++; - if(mWave_TND.nDMCDMAAddr & 0x1000) - { - mWave_TND.nDMCDMAAddr &= 0x0FFF; - mWave_TND.nDMCDMABank = - (mWave_TND.nDMCDMABank + 1) & 0x07; - } - - mWave_TND.bDMCSampleBufferEmpty = 0; - mWave_TND.nDMCBytesRemaining--; - if(!mWave_TND.nDMCBytesRemaining) - { - if(mWave_TND.bDMCLoop) - { - mWave_TND.nDMCDMABank = mWave_TND.nDMCDMABank_Load; - mWave_TND.nDMCDMAAddr = mWave_TND.nDMCDMAAddr_Load; - mWave_TND.nDMCBytesRemaining =mWave_TND.nDMCLength; - } - else if(mWave_TND.bDMCIRQEnabled) - mWave_TND.bDMCIRQPending = 1; - } - } - - if(!mWave_TND.nDMCDeltaBit) - { - mWave_TND.nDMCDeltaBit = 8; - mWave_TND.bDMCDeltaSilent =mWave_TND.bDMCSampleBufferEmpty; - mWave_TND.nDMCDelta = mWave_TND.nDMCSampleBuffer; - mWave_TND.bDMCSampleBufferEmpty = 1; - } - - if(mWave_TND.nDMCDeltaBit) { - mWave_TND.nDMCDeltaBit--; - if(!mWave_TND.bDMCDeltaSilent) - { - if(mWave_TND.nDMCDelta & 0x01) - { - if(mWave_TND.nDMCOutput < 0x7E) - mWave_TND.nDMCOutput += 2; - } - else if(mWave_TND.nDMCOutput > 1) - mWave_TND.nDMCOutput -= 2; - } - mWave_TND.nDMCDelta >>= 1; - } - - if(!mWave_TND.nDMCBytesRemaining && - mWave_TND.bDMCSampleBufferEmpty && - mWave_TND.bDMCDeltaSilent) - mWave_TND.bDMCActive = mWave_TND.nDMCDeltaBit = 0; - } - } - - EXIT_TIMER(tnd_dmc); - - /* end of TND generation */ - EXIT_TIMER(tnd); - - if(nExternalSound && !bPALMode) - { - if(nExternalSound & EXTSOUND_VRC6) - Wave_VRC6_DoTicks(tick); - if(nExternalSound & EXTSOUND_N106) - Wave_N106_DoTicks(tick); - if(nExternalSound & EXTSOUND_FME07) - { - if (mWave_FME07[0].bChannelEnabled && - mWave_FME07[0].nFreqTimer.W) { - mWave_FME07[0].nFreqCount -= tick; - - if(mWave_FME07[0].nDutyCount < 16) - { - mWave_FME07[0].nMixL = - FME07_nOutputTable_L[mWave_FME07[0].nVolume]; - } else mWave_FME07[0].nMixL = 0; - while(mWave_FME07[0].nFreqCount <= 0) { - mWave_FME07[0].nFreqCount += - mWave_FME07[0].nFreqTimer.W; - - mWave_FME07[0].nDutyCount= - (mWave_FME07[0].nDutyCount+1)&0x1f; - } - } - - if (mWave_FME07[1].bChannelEnabled && - mWave_FME07[1].nFreqTimer.W) { - mWave_FME07[1].nFreqCount -= tick; - - if(mWave_FME07[1].nDutyCount < 16) - { - mWave_FME07[1].nMixL = - FME07_nOutputTable_L[mWave_FME07[1].nVolume]; - } else mWave_FME07[1].nMixL = 0; - while(mWave_FME07[1].nFreqCount <= 0) { - mWave_FME07[1].nFreqCount += - mWave_FME07[1].nFreqTimer.W; - - mWave_FME07[1].nDutyCount= - (mWave_FME07[1].nDutyCount+1)&0x1f; - } - } - - if (mWave_FME07[2].bChannelEnabled && - mWave_FME07[2].nFreqTimer.W) { - mWave_FME07[2].nFreqCount -= tick; - - if(mWave_FME07[2].nDutyCount < 16) - { - mWave_FME07[2].nMixL = - FME07_nOutputTable_L[mWave_FME07[2].nVolume]; - } else mWave_FME07[2].nMixL = 0; - while(mWave_FME07[2].nFreqCount <= 0) { - mWave_FME07[2].nFreqCount += - mWave_FME07[2].nFreqTimer.W; - - mWave_FME07[2].nDutyCount= - (mWave_FME07[2].nDutyCount+1)&0x1f; - } - } - - } /* end FME07 */ - ENTER_TIMER(fds); - if(nExternalSound & EXTSOUND_FDS) { - - /* Volume Envelope Unit */ - if(mWave_FDS.bVolEnv_On) - { - mWave_FDS.nVolEnv_Count -= tick; - while(mWave_FDS.nVolEnv_Count <= 0) - { - mWave_FDS.nVolEnv_Count += mWave_FDS.nVolEnv_Timer; - if(mWave_FDS.nVolEnv_Mode) { - if(mWave_FDS.nVolEnv_Gain < 0x20) - mWave_FDS.nVolEnv_Gain++; - } - else { - if(mWave_FDS.nVolEnv_Gain) - mWave_FDS.nVolEnv_Gain--; - } - } - } - - /* Sweep Envelope Unit */ - if(mWave_FDS.bSweepEnv_On) - { - mWave_FDS.nSweep_Count -= tick; - while(mWave_FDS.nSweep_Count <= 0) - { - mWave_FDS.nSweep_Count += mWave_FDS.nSweep_Timer; - if(mWave_FDS.nSweep_Mode) { - if(mWave_FDS.nSweep_Gain < 0x20) - mWave_FDS.nSweep_Gain++; - } else { - if(mWave_FDS.nSweep_Gain) mWave_FDS.nSweep_Gain--; - } - } - } - - /* Effector / LFO */ - int32_t subfreq = 0; - if(mWave_FDS.bLFO_On) - { - mWave_FDS.nLFO_Count -= tick<<14; - while(mWave_FDS.nLFO_Count <= 0) - { - mWave_FDS.nLFO_Count += mWave_FDS.nLFO_Timer; - if(mWave_FDS.nLFO_Table[mWave_FDS.nLFO_Addr] == 4) - mWave_FDS.nSweepBias = 0; - else - mWave_FDS.nSweepBias += - ModulationTable[ - mWave_FDS.nLFO_Table[mWave_FDS.nLFO_Addr] - ]; - mWave_FDS.nLFO_Addr = (mWave_FDS.nLFO_Addr + 1) & 0x3F; - } - - while(mWave_FDS.nSweepBias > 63) - mWave_FDS.nSweepBias -= 128; - while(mWave_FDS.nSweepBias < -64) - mWave_FDS.nSweepBias += 128; - - register int32_t temp = - mWave_FDS.nSweepBias * mWave_FDS.nSweep_Gain; - if(temp & 0x0F) - { - temp /= 16; - if(mWave_FDS.nSweepBias < 0) temp--; - else temp += 2; - } - else - temp /= 16; - - if(temp > 193) temp -= 258; - if(temp < -64) temp += 256; - - subfreq = mWave_FDS.nFreq.W * temp / 64; - } - - /* Main Unit */ - if(mWave_FDS.bMain_On) - { - mWave_FDS.nMixL = - FDS_nOutputTable_L[mWave_FDS.nMainVolume] - [mWave_FDS.nVolume] - [mWave_FDS.nWaveTable[mWave_FDS.nMainAddr] ]; - - if((subfreq + mWave_FDS.nFreq.W) > 0) - { - int32_t freq = (0x10000<<14) / (subfreq + mWave_FDS.nFreq.W); - - mWave_FDS.nFreqCount -= tick<<14; - while(mWave_FDS.nFreqCount <= 0) - { - mWave_FDS.nFreqCount += freq; - - mWave_FDS.nMainAddr = - (mWave_FDS.nMainAddr + 1) & 0x3F; - mWave_FDS.nPopOutput = - mWave_FDS.nWaveTable[mWave_FDS.nMainAddr]; - if(!mWave_FDS.nMainAddr) - { - if(mWave_FDS.nVolEnv_Gain < 0x20) - mWave_FDS.nVolume = mWave_FDS.nVolEnv_Gain; - else mWave_FDS.nVolume = 0x20; - } - } - } - else - mWave_FDS.nFreqCount = mWave_FDS.nLFO_Count; - } - else if(mWave_FDS.bPopReducer && mWave_FDS.nPopOutput) - { - mWave_FDS.nMixL = FDS_nOutputTable_L[mWave_FDS.nMainVolume] - [mWave_FDS.nVolume] - [mWave_FDS.nPopOutput]; - - mWave_FDS.nPopCount -= tick; - while(mWave_FDS.nPopCount <= 0) - { - mWave_FDS.nPopCount += 500; - mWave_FDS.nPopOutput--; - if(!mWave_FDS.nPopOutput) - mWave_FDS.nMainAddr = 0; - } - } /* end FDS */ - } - EXIT_TIMER(fds); - } /* end while fulltick */ - - if(bBurnCPUCycles) - { - nCPUCycle += burned; - fulltick += burned; - } - - /* Frame Sequencer */ - - ENTER_TIMER(frame); - nTicksUntilNextFrame -= tick<<16; - while(nTicksUntilNextFrame <= 0) - { - nTicksUntilNextFrame += - (bPALMode ? PAL_FRAME_COUNTER_FREQ : NTSC_FRAME_COUNTER_FREQ) * - 0x10000; - nFrameCounter++; - if(nFrameCounter > nFrameCounterMax) - nFrameCounter = 0; - - if(nFrameCounterMax == 4) - { - if(nFrameCounter < 4) - { - CLOCK_MAJOR(); - if(!(nFrameCounter & 1)) - CLOCK_MINOR(); - } - } - else - { - CLOCK_MAJOR(); - if(nFrameCounter & 1) - CLOCK_MINOR(); - - if((nFrameCounter == 3) && bFrameIRQEnabled) - bFrameIRQPending = 1; - } - } - EXIT_TIMER(frame); - - ENTER_TIMER(mix); - nTicksUntilNextSample -= tick<<16; - if(nTicksUntilNextSample <= 0) - { - nTicksUntilNextSample += nTicksPerSample; - - mixL = mWave_Squares.nMixL; - mixL += mWave_TND.nMixL; - - if(nExternalSound && !bPALMode) - { - if(nExternalSound & EXTSOUND_VRC6) - { - mixL += (mWave_VRC6Pulse[0].nMixL); - mixL += (mWave_VRC6Pulse[1].nMixL); - mixL += (mWave_VRC6Saw.nMixL); - } - if(nExternalSound & EXTSOUND_N106) { - mixL += (mWave_N106.nMixL[0]); - mixL += (mWave_N106.nMixL[1]); - mixL += (mWave_N106.nMixL[2]); - mixL += (mWave_N106.nMixL[3]); - mixL += (mWave_N106.nMixL[4]); - mixL += (mWave_N106.nMixL[5]); - mixL += (mWave_N106.nMixL[6]); - mixL += (mWave_N106.nMixL[7]); - } - if(nExternalSound & EXTSOUND_FME07) - { - mixL += (mWave_FME07[0].nMixL); - mixL += (mWave_FME07[1].nMixL); - mixL += (mWave_FME07[2].nMixL); - } - if(nExternalSound & EXTSOUND_FDS) - mixL += mWave_FDS.nMixL; - } - - /* Filter */ - diff = ((int64_t)mixL << 25) - nFilterAccL; - nFilterAccL += (diff * nHighPass) >> 16; - mixL = (int32_t)(diff >> 23); - /* End Filter */ - - if(bFade && (fFadeVolume < 1)) - mixL = (int32_t)(mixL * fFadeVolume); - - if(mixL < -32768) mixL = -32768; - if(mixL > 32767) mixL = 32767; - - *((uint16_t*)pOutput) = (uint16_t)mixL; - pOutput += 2; - } - - } - EXIT_TIMER(mix); - - nAPUCycle = nCPUCycle; - - EXIT_TIMER(apu); -} - - -/* - * Initialize - * - * Initializes Memory - */ - -static int NSFCore_Initialize() -{ - int32_t i; - /* clear globals */ - /* why, yes, this was easier when they were in a struct */ - - /* - * Memory - */ - - ZEROMEMORY(pRAM,0x800); - ZEROMEMORY(pSRAM,0x2000); - ZEROMEMORY(pExRAM,0x1000); - pROM_Full=0; - - ZEROMEMORY(pROM,10); - pStack=0; - - nROMSize=0; - nROMBankCount=0; - nROMMaxSize=0; - - /* - * Memory Proc Pointers - */ - - ZEROMEMORY(ReadMemory,sizeof(ReadProc)*0x10); - ZEROMEMORY(WriteMemory,sizeof(WriteProc)*0x10); - - /* - * 6502 Registers / Mode - */ - - regA=0; - regX=0; - regY=0; - regP=0; - regSP=0; - regPC=0; - - bPALMode=0; - bCPUJammed=0; - - nMultIn_Low=0; - nMultIn_High=0; - - /* - * NSF Preparation Information - */ - - ZEROMEMORY(nBankswitchInitValues,10); - nPlayAddress=0; - nInitAddress=0; - - nExternalSound=0; - nCurTrack=0; - - fNSFPlaybackSpeed=0; - - /* - * pAPU - */ - - nFrameCounter=0; - nFrameCounterMax=0; - bFrameIRQEnabled=0; - bFrameIRQPending=0; - - /* - * Timing and Counters - */ - nTicksUntilNextFrame=0; - - nTicksPerPlay=0; - nTicksUntilNextPlay=0; - - nTicksPerSample=0; - nTicksUntilNextSample=0; - - nCPUCycle=0; - nAPUCycle=0; - nTotalPlays=0; - - /* - * Silence Tracker - */ - nSilentSamples=0; - nSilentSampleMax=0; - nSilenceTrackMS=0; - bNoSilenceIfTime=0; - bTimeNotDefault=0; - - /* - * Volume/fading/filter tracking - */ - - nStartFade=0; - nEndFade=0; - bFade=0; - fFadeVolume=0; - fFadeChange=0; - - pOutput=0; - - nDMCPop_Prev=0; - bDMCPop_Skip=0; - bDMCPop_SamePlay=0; - - /* - * Sound Filter - */ - - nFilterAccL=0; - nHighPass=0; - - nHighPassBase=0; - - bHighPassEnabled=0; - - /* channels */ - - ZEROMEMORY(&mWave_Squares,sizeof(struct Wave_Squares)); - ZEROMEMORY(&mWave_TND,sizeof(struct Wave_TND)); - ZEROMEMORY(mWave_VRC6Pulse,sizeof(struct VRC6PulseWave)*2); - ZEROMEMORY(&mWave_VRC6Saw,sizeof(struct VRC6SawWave)); - ZEROMEMORY(&mWave_N106,sizeof(struct N106Wave)); - ZEROMEMORY(mWave_FME07,sizeof(struct FME07Wave)*3); - ZEROMEMORY(&mWave_FDS,sizeof(struct FDSWave)); - - /* end clear globals */ - - // Default filter bases - nHighPassBase = 150; - - bHighPassEnabled = 1; - - mWave_TND.nNoiseRandomShift = 1; - for(i = 0; i < 8; i++) - mWave_TND.pDMCDMAPtr[i] = pROM[i + 2]; - - - SetPlaybackOptions(nSampleRate); - - for(i = 0; i < 8; i++) - mWave_N106.nFrequencyLookupTable[i] = - ((((i + 1) * 45 * 0x40000) / (float)NES_FREQUENCY) * - (float)NTSC_FREQUENCY) * 256.0; - - ZEROMEMORY(pRAM,0x800); - ZEROMEMORY(pSRAM,0x2000); - ZEROMEMORY(pExRAM,0x1000); - pStack = pRAM + 0x100; - return 1; -} - -/* - * LoadNSF - */ - -static int LoadNSF(int32_t datasize) -{ - if(!pDataBuffer) return 0; - - int32_t i; - - nExternalSound = nChipExtensions; - if(nIsPal & 2) - bPALMode = bPALPreference; - else - bPALMode = nIsPal & 1; - - SetPlaybackOptions(nSampleRate); - - int32_t neededsize = datasize + (nfileLoadAddress & 0x0FFF); - if(neededsize & 0x0FFF) neededsize += 0x1000 - (neededsize & 0x0FFF); - if(neededsize < 0x1000) neededsize = 0x1000; - - uint8_t specialload = 0; - - for(i = 0; (i < 8) && (!nBankswitch[i]); i++); - if(i < 8) /* uses bankswitching */ - { - memcpy(&nBankswitchInitValues[2],nBankswitch,8); - nBankswitchInitValues[0] = nBankswitch[6]; - nBankswitchInitValues[1] = nBankswitch[7]; - if(nExternalSound & EXTSOUND_FDS) - { - if(!(nBankswitchInitValues[0] || nBankswitchInitValues[1])) - { - /* - * FDS sound with '00' specified for both $6000 and $7000 banks. - * point this to an area of fresh RAM (sort of hackish solution - * for those FDS tunes that don't quite follow the nsf specs. - */ - nBankswitchInitValues[0] = (uint8_t)(neededsize >> 12); - nBankswitchInitValues[1] = (uint8_t)(neededsize >> 12) + 1; - neededsize += 0x2000; - } - } - } - else /* doesn't use bankswitching */ - { - if(nExternalSound & EXTSOUND_FDS) - { - /* bad load address */ - if(nfileLoadAddress < 0x6000) return 0; - - if(neededsize < 0xA000) - neededsize = 0xA000; - specialload = 1; - for(i = 0; i < 10; i++) - nBankswitchInitValues[i] = (uint8_t)i; - } - else - { - /* bad load address */ - if(nfileLoadAddress < 0x8000) return 0; - - int32_t j = (nfileLoadAddress >> 12) - 6; - for(i = 0; i < j; i++) - nBankswitchInitValues[i] = 0; - for(j = 0; i < 10; i++, j++) - nBankswitchInitValues[i] = (uint8_t)j; - } - } - - nROMSize = neededsize; - nROMBankCount = neededsize >> 12; - - if(specialload) - pROM_Full = pDataBuffer-(nfileLoadAddress-0x6000); - else - pROM_Full = pDataBuffer-(nfileLoadAddress&0x0FFF); - - ZEROMEMORY(pRAM,0x0800); - ZEROMEMORY(pExRAM,0x1000); - ZEROMEMORY(pSRAM,0x2000); - - nExternalSound = nChipExtensions; - fNSFPlaybackSpeed = (bPALMode ? PAL_NMIRATE : NTSC_NMIRATE); - - SetPlaybackSpeed(0); - - nPlayAddress = nfilePlayAddress; - nInitAddress = nfileInitAddress; - - pExRAM[0x00] = 0x20; /* JSR */ - pExRAM[0x01] = nInitAddress&0xff; /* Init Address */ - pExRAM[0x02] = (nInitAddress>>8)&0xff; - pExRAM[0x03] = 0xF2; /* JAM */ - pExRAM[0x04] = 0x20; /* JSR */ - pExRAM[0x05] = nPlayAddress&0xff; /* Play Address */ - pExRAM[0x06] = (nPlayAddress>>8)&0xff; - pExRAM[0x07] = 0x4C; /* JMP */ - pExRAM[0x08] = 0x03;/* $5003 (JAM right before the JSR to play address) */ - pExRAM[0x09] = 0x50; - - regA = regX = regY = 0; - regP = 0x04; /* I_FLAG */ - regSP = 0xFF; - - nFilterAccL = 0; - - /* Reset Read/Write Procs */ - - ReadMemory[0] = ReadMemory[1] = ReadMemory_RAM; - ReadMemory[2] = ReadMemory[3] = ReadMemory_Default; - ReadMemory[4] = ReadMemory_pAPU; - ReadMemory[5] = ReadMemory_ExRAM; - ReadMemory[6] = ReadMemory[7] = ReadMemory_SRAM; - - WriteMemory[0] = WriteMemory[1] = WriteMemory_RAM; - WriteMemory[2] = WriteMemory[3] = WriteMemory_Default; - WriteMemory[4] = WriteMemory_pAPU; - WriteMemory[5] = WriteMemory_ExRAM; - WriteMemory[6] = WriteMemory[7] = WriteMemory_SRAM; - - for(i = 8; i < 16; i++) - { - ReadMemory[i] = ReadMemory_ROM; - WriteMemory[i] = WriteMemory_Default; - } - - if(nExternalSound & EXTSOUND_FDS) - { - WriteMemory[0x06] = WriteMemory_FDSRAM; - WriteMemory[0x07] = WriteMemory_FDSRAM; - WriteMemory[0x08] = WriteMemory_FDSRAM; - WriteMemory[0x09] = WriteMemory_FDSRAM; - WriteMemory[0x0A] = WriteMemory_FDSRAM; - WriteMemory[0x0B] = WriteMemory_FDSRAM; - WriteMemory[0x0C] = WriteMemory_FDSRAM; - WriteMemory[0x0D] = WriteMemory_FDSRAM; - ReadMemory[0x06] = ReadMemory_ROM; - ReadMemory[0x07] = ReadMemory_ROM; - } - - if(!bPALMode) /* no expansion sound available on a PAL system */ - { - if(nExternalSound & EXTSOUND_VRC6) - { - /* if both VRC6+VRC7... it MUST go to WriteMemory_VRC6 - * or register writes will be lost (WriteMemory_VRC6 calls - * WriteMemory_VRC7 if needed) */ - WriteMemory[0x09] = WriteMemory_VRC6; - WriteMemory[0x0A] = WriteMemory_VRC6; - WriteMemory[0x0B] = WriteMemory_VRC6; - } - if(nExternalSound & EXTSOUND_N106) - { - WriteMemory[0x04] = WriteMemory_N106; - ReadMemory[0x04] = ReadMemory_N106; - WriteMemory[0x0F] = WriteMemory_N106; - } - if(nExternalSound & EXTSOUND_FME07) - { - WriteMemory[0x0C] = WriteMemory_FME07; - WriteMemory[0x0E] = WriteMemory_FME07; - } - } - - /* MMC5 still has a multiplication reg that needs to be available on - PAL tunes */ - if(nExternalSound & EXTSOUND_MMC5) - WriteMemory[0x05] = WriteMemory_MMC5; - - return 1; -} - -/* - * SetTrack - */ - -static void SetTrack(uint8_t track) -{ - int32_t i; - - nCurTrack = track; - - regPC = 0x5000; - regA = track; - regX = bPALMode; - regY = bCleanAXY ? 0 : 0xCD; - regSP = 0xFF; - if(bCleanAXY) - regP = 0x04; - bCPUJammed = 0; - - nCPUCycle = nAPUCycle = 0; - nDMCPop_Prev = 0; - bDMCPop_Skip = 0; - - for(i = 0x4000; i < 0x400F; i++) - WriteMemory_pAPU(i,0); - WriteMemory_pAPU(0x4010,0); - WriteMemory_pAPU(0x4012,0); - WriteMemory_pAPU(0x4013,0); - WriteMemory_pAPU(0x4014,0); - WriteMemory_pAPU(0x4015,0); - WriteMemory_pAPU(0x4015,0x0F); - WriteMemory_pAPU(0x4017,0); - - for(i = 0; i < 10; i++) - WriteMemory_ExRAM(0x5FF6 + i,nBankswitchInitValues[i]); - - ZEROMEMORY(pRAM,0x0800); - ZEROMEMORY(pSRAM,0x2000); - ZEROMEMORY(&pExRAM[0x10],0x0FF0); - bFade = 0; - - - nTicksUntilNextSample = nTicksPerSample; - nTicksUntilNextFrame = - (bPALMode ? PAL_FRAME_COUNTER_FREQ : NTSC_FRAME_COUNTER_FREQ)*0x10000; - nTicksUntilNextPlay = nTicksPerPlay; - nTotalPlays = 0; - - /* Clear mixing vals */ - mWave_Squares.nMixL = 0; - mWave_TND.nMixL = 0; - mWave_VRC6Pulse[0].nMixL = 0; - mWave_VRC6Pulse[1].nMixL = 0; - mWave_VRC6Saw.nMixL = 0; - - /* Reset Tri/Noise/DMC */ - mWave_TND.nTriStep = mWave_TND.nTriOutput = 0; - mWave_TND.nDMCOutput = 0; - mWave_TND.bNoiseRandomOut = 0; - mWave_Squares.nDutyCount[0] = mWave_Squares.nDutyCount[1] = 0; - mWave_TND.bDMCActive = 0; - mWave_TND.nDMCBytesRemaining = 0; - mWave_TND.bDMCSampleBufferEmpty = 1; - mWave_TND.bDMCDeltaSilent = 1; - - /* Reset VRC6 */ - mWave_VRC6Pulse[0].nVolume = 0; - mWave_VRC6Pulse[1].nVolume = 0; - mWave_VRC6Saw.nAccumRate = 0; - - /* Reset N106 */ - ZEROMEMORY(mWave_N106.nRAM,0x100); - ZEROMEMORY(mWave_N106.nVolume,8); - ZEROMEMORY(mWave_N106.nOutput,8); - ZEROMEMORY(mWave_N106.nMixL,32); - - /* Reset FME-07 */ - mWave_FME07[0].nVolume = 0; - mWave_FME07[1].nVolume = 0; - mWave_FME07[2].nVolume = 0; - - /* Clear FDS crap */ - - mWave_FDS.bEnvelopeEnable = 0; - mWave_FDS.nEnvelopeSpeed = 0xFF; - mWave_FDS.nVolEnv_Mode = 2; - mWave_FDS.nVolEnv_Decay = 0; - mWave_FDS.nVolEnv_Gain = 0; - mWave_FDS.nVolume = 0; - mWave_FDS.bVolEnv_On = 0; - mWave_FDS.nSweep_Mode = 2; - mWave_FDS.nSweep_Decay = 0; - mWave_FDS.nSweep_Gain = 0; - mWave_FDS.bSweepEnv_On = 0; - mWave_FDS.nSweepBias = 0; - mWave_FDS.bLFO_Enabled = 0; - mWave_FDS.nLFO_Freq.W = 0; -/* mWave_FDS.fLFO_Timer = 0; - mWave_FDS.fLFO_Count = 0;*/ - mWave_FDS.nLFO_Timer = 0; - mWave_FDS.nLFO_Count = 0; - mWave_FDS.nLFO_Addr = 0; - mWave_FDS.bLFO_On = 0; - mWave_FDS.nMainVolume = 0; - mWave_FDS.bEnabled = 0; - mWave_FDS.nFreq.W = 0; -/* mWave_FDS.fFreqCount = 0;*/ - mWave_FDS.nFreqCount = 0; - mWave_FDS.nMainAddr = 0; - mWave_FDS.bWaveWrite = 0; - mWave_FDS.bMain_On = 0; - mWave_FDS.nMixL = 0; - ZEROMEMORY(mWave_FDS.nWaveTable,0x40); - ZEROMEMORY(mWave_FDS.nLFO_Table,0x40); - - mWave_FDS.nSweep_Count = mWave_FDS.nSweep_Timer = - ((mWave_FDS.nSweep_Decay + 1) * mWave_FDS.nEnvelopeSpeed * 8); - mWave_FDS.nVolEnv_Count = mWave_FDS.nVolEnv_Timer = - ((mWave_FDS.nVolEnv_Decay + 1) * mWave_FDS.nEnvelopeSpeed * 8); - - nSilentSamples = 0; +#include +#include "libgme/nsf_emu.h" - nFilterAccL = 0; - - nSilentSamples = 0; - - fulltick=0; -} - -/* - * SetPlaybackOptions - */ - -static int SetPlaybackOptions(int32_t samplerate) -{ - if(samplerate < 2000) return 0; - if(samplerate > 96000) return 0; - - nTicksPerSample = - (bPALMode ? PAL_FREQUENCY : NTSC_FREQUENCY) / samplerate * 0x10000; - nTicksUntilNextSample = nTicksPerSample; - - RecalcFilter(); - RecalcSilenceTracker(); - - return 1; -} - -/* - * SetPlaybackSpeed - */ - -static void SetPlaybackSpeed(float playspersec) -{ - if(playspersec < 1) - { - playspersec = fNSFPlaybackSpeed; - } - - nTicksPerPlay = nTicksUntilNextPlay = - (bPALMode ? PAL_FREQUENCY : NTSC_FREQUENCY) / playspersec * 0x10000; -} - -/* -* GetPlaybackSpeed -*/ - -static float GetPlaybackSpeed() -{ - if(nTicksPerPlay <= 0) return 0; - return ((bPALMode ? PAL_FREQUENCY : NTSC_FREQUENCY) / (nTicksPerPlay>>16)); -} - -/* - * RecalcFilter - */ - -static void RecalcFilter() -{ - if(!nSampleRate) return; - - nHighPass = ((int64_t)nHighPassBase << 16) / nSampleRate; - - if(nHighPass > (1<<16)) nHighPass = 1<<16; -} - -/* - * RecalcSilenceTracker - */ - -static void RecalcSilenceTracker() -{ - if(nSilenceTrackMS <= 0 || !nSampleRate || - (bNoSilenceIfTime && bTimeNotDefault)) - { - nSilentSampleMax = 0; - return; - } - - nSilentSampleMax = nSilenceTrackMS * nSampleRate / 500; - nSilentSampleMax /= 2; -} - -static void RebuildOutputTables(void) { - int32_t i,j; - float l[3]; - int32_t temp; - float ftemp; - - /* tnd */ - for(i = 0; i < 3; i++) - { - l[i] = 255; - } - - for(i = 0; i < 0x8000; i++) - { - ftemp = (l[0] * (i >> 11)) / 2097885; - ftemp += (l[1] * ((i >> 7) & 0x0F)) / 3121455; - ftemp += (l[2] * (i & 0x7F)) / 5772690; - - if(!ftemp) - main_nOutputTable_L[i] = 0; - else - main_nOutputTable_L[i] = - (int16_t)(2396850 / ((1.0f / ftemp) + 100)); - } - - /* squares */ - for(i = 0; i < 2; i++) - { - l[i] = 255; - } - - for(j = 0; j < 0x10; j++) - { - for(i = 0; i < 0x10; i++) - { - temp = (int32_t)(l[0] * j); - temp += (int32_t)(l[1] * i); - - if(!temp) - Squares_nOutputTable_L[j][i] = 0; - else - Squares_nOutputTable_L[j][i] = 1438200 / ((2072640 / temp) + 100); - } - } - - /* VRC6 Pulse 1,2 */ - for(i = 0; i < 0x10; i++) - { - VRC6Pulse_nOutputTable_L[i] = - 1875 * i / 0x0F; - } - /* VRC6 Saw */ - for(i = 0; i < 0x20; i++) - { - VRC6Saw_nOutputTable_L[i] = 3750 * i / 0x1F; - } - - /* N106 channels */ - /* this amplitude is just a guess */ - - for(i = 0; i < 0x10; i++) - { - for(j = 0; j < 0x10; j++) - { - N106_nOutputTable_L[i][j] = (3000 * i * j) / 0xE1; - } - } - - /* FME-07 Square A,B,C */ - FME07_nOutputTable_L[15] = 3000; - FME07_nOutputTable_L[0] = 0; - for(i = 14; i > 0; i--) - { - FME07_nOutputTable_L[i] = FME07_nOutputTable_L[i + 1] * 80 / 100; - } - - /* - * FDS - */ - /* this base volume (4000) is just a guess to what sounds right. - * Given the number of steps available in an FDS wave... it seems like - * it should be much much more... but then it's TOO loud. - */ - for(i = 0; i < 0x21; i++) - { - for(j = 0; j < 0x40; j++) - { - FDS_nOutputTable_L[0][i][j] = - (4000 * i * j * 30) / (0x21 * 0x40 * 30); - FDS_nOutputTable_L[1][i][j] = - (4000 * i * j * 20) / (0x21 * 0x40 * 30); - FDS_nOutputTable_L[2][i][j] = - (4000 * i * j * 15) / (0x21 * 0x40 * 30); - FDS_nOutputTable_L[3][i][j] = - (4000 * i * j * 12) / (0x21 * 0x40 * 30); - } - } -} - -/* rockbox: not used */ -#if 0 -/* - * GetPlayCalls - */ - -float GetPlayCalls() -{ - if(!nTicksPerPlay) return 0; - - return ((float)nTotalPlays) + - (1.0f - (nTicksUntilNextPlay*1.0f / nTicksPerPlay)); -} - -/* - * GetWrittenTime - */ -uint32_t GetWrittenTime(float basedplayspersec /* = 0 */) -{ - if(basedplayspersec <= 0) - basedplayspersec = GetPlaybackSpeed(); - - if(basedplayspersec <= 0) - return 0; - - return (uint32_t)((GetPlayCalls() * 1000) / basedplayspersec); -} - -/* - * StopFade - */ -void StopFade() -{ - bFade = 0; - fFadeVolume = 1; -} -#endif - -/* - * SongCompleted - */ - -static uint8_t SongCompleted() -{ - if(!bFade) return 0; - if(nTotalPlays >= nEndFade) return 1; - if(nSilentSampleMax) return (nSilentSamples >= nSilentSampleMax); - - return 0; -} - -/* - * SetFade - */ - -static void SetFade(int32_t fadestart,int32_t fadestop, - uint8_t bNotDefault) /* play routine calls */ -{ - if(fadestart < 0) fadestart = 0; - if(fadestop < fadestart) fadestop = fadestart; - - nStartFade = (uint32_t)fadestart; - nEndFade = (uint32_t)fadestop; - bFade = 1; - bTimeNotDefault = bNotDefault; - - RecalcSilenceTracker(); - RecalculateFade(); -} - -/* - * SetFadeTime - */ - -static void SetFadeTime(uint32_t fadestart,uint32_t fadestop,float basedplays, - uint8_t bNotDefault) /* time in MS */ -{ - if(basedplays <= 0) - basedplays = GetPlaybackSpeed(); - if(basedplays <= 0) - return; - - SetFade((int32_t)(fadestart * basedplays / 1000), - (int32_t)(fadestop * basedplays / 1000),bNotDefault); -} - -/* - * RecalculateFade - */ - -static void RecalculateFade() -{ - if(!bFade) return; - - /* make it hit silence a little before the song ends... - otherwise we're not really fading OUT, we're just fading umm... - quieter =P */ - int32_t temp = (int32_t)(GetPlaybackSpeed() / 4); - - if(nEndFade <= nStartFade) - { - nEndFade = nStartFade; - fFadeChange = 1.0f; - } - else if((nEndFade - temp) <= nStartFade) - fFadeChange = 1.0f; - else - fFadeChange = 1.0f / (nEndFade - nStartFade - temp); - - if(nTotalPlays < nStartFade) - fFadeVolume = 1.0f; - else if(nTotalPlays >= nEndFade) - fFadeVolume = 0.0f; - else - { - fFadeVolume = 1.0f - ( (nTotalPlays - nStartFade + 1) * fFadeChange ); - if(fFadeVolume < 0) - fFadeVolume = 0; - } - -} - -static int32_t GetSamples(uint8_t* buffer,int32_t buffersize) -{ - if(!buffer) return 0; - if(buffersize < 16) return 0; - if(bFade && (nTotalPlays >= nEndFade)) return 0; - - pOutput = buffer; - uint32_t runtocycle = - (uint32_t)((buffersize / 2) * nTicksPerSample / 0x10000); - nCPUCycle = nAPUCycle = 0; - uint32_t tick; - - while(1) - { - /*tick = (uint32_t)ceil(fTicksUntilNextPlay);*/ - tick = (nTicksUntilNextPlay+0xffff)>>16; - if((tick + nCPUCycle) > runtocycle) - tick = runtocycle - nCPUCycle; - - if(bCPUJammed) - { - nCPUCycle += tick; - EmulateAPU(0); - } - else - { - tick = Emulate6502(tick + nCPUCycle); - EmulateAPU(1); - } - - nTicksUntilNextPlay -= tick<<16; - if(nTicksUntilNextPlay <= 0) - { - nTicksUntilNextPlay += nTicksPerPlay; - if((bCPUJammed == 2) || bNoWaitForReturn) - { - regX = regY = regA = (bCleanAXY ? 0 : 0xCD); - regPC = 0x5004; - nTotalPlays++; - bDMCPop_SamePlay = 0; - bCPUJammed = 0; - if(nForce4017Write == 1) WriteMemory_pAPU(0x4017,0x00); - if(nForce4017Write == 2) WriteMemory_pAPU(0x4017,0x80); - } - - if(bFade && (nTotalPlays >= nStartFade)) - { - fFadeVolume -= fFadeChange; - if(fFadeVolume < 0) - fFadeVolume = 0; - if(nTotalPlays >= nEndFade) - break; - } - } - - if(nCPUCycle >= runtocycle) - break; - } - - nCPUCycle = nAPUCycle = 0; - - if(nSilentSampleMax && bFade) - { - int16_t* tempbuf = (int16_t*)buffer; - while( ((uint8_t*)tempbuf) < pOutput) - { - if( (*tempbuf < -SILENCE_THRESHOLD) || - (*tempbuf > SILENCE_THRESHOLD) ) - nSilentSamples = 0; - else - { - if(++nSilentSamples >= nSilentSampleMax) - return (int32_t)( ((uint8_t*)tempbuf) - buffer); - } - tempbuf++; - } - } - - return (int32_t)(pOutput - buffer); -} - -/****************** 6502 emulation ******************/ - -/* Memory reading/writing and other defines */ - -/* reads zero page memory */ -#define Zp(a) pRAM[a] -/* reads zero page memory in word form */ -#define ZpWord(a) (Zp(a) | (Zp((uint8_t)(a + 1)) << 8)) -/* reads memory */ -#define Rd(a) ((ReadMemory[((uint16_t)(a)) >> 12])(a)) -/* reads memory in word form */ -#define RdWord(a) (Rd(a) | (Rd(a + 1) << 8)) -/* writes memory */ -#define Wr(a,v) (WriteMemory[((uint16_t)(a)) >> 12])(a,v) -/* writes zero paged memory */ -#define WrZ(a,v) pRAM[a] = v -/* pushes a value onto the stack */ -#define PUSH(v) pStack[SP--] = v -/* pulls a value from the stack */ -#define PULL(v) v = pStack[++SP] - -/* Addressing Modes */ - -/* first set - gets the value that's being addressed */ -/*Immediate*/ -#define Ad_VlIm() val = Rd(PC.W); PC.W++ -/*Zero Page*/ -#define Ad_VlZp() final.W = Rd(PC.W); val = Zp(final.W); PC.W++ -/*Zero Page, X*/ -#define Ad_VlZx() front.W = final.W = Rd(PC.W); final.B.l += X; \ - val = Zp(final.B.l); PC.W++ -/*Zero Page, Y*/ -#define Ad_VlZy() front.W = final.W = Rd(PC.W); final.B.l += Y; \ - val = Zp(final.B.l); PC.W++ -/*Absolute*/ -#define Ad_VlAb() final.W = RdWord(PC.W); val = Rd(final.W); PC.W += 2 -/*Absolute, X [uses extra cycle if crossed page]*/ -#define Ad_VlAx() front.W = final.W = RdWord(PC.W); final.W += X; PC.W += 2;\ - if(front.B.h != final.B.h) nCPUCycle++; val = Rd(final.W) -/*Absolute, X [uses extra cycle if crossed page]*/ -#define Ad_VlAy() front.W = final.W = RdWord(PC.W); final.W += Y; PC.W += 2;\ - if(front.B.h != final.B.h) nCPUCycle++; val = Rd(final.W) -/*(Indirect, X)*/ -#define Ad_VlIx() front.W = final.W = Rd(PC.W); final.B.l += X; PC.W++; \ - final.W = ZpWord(final.B.l); val = Rd(final.W) -/*(Indirect), Y [uses extra cycle if crossed page]*/ -#define Ad_VlIy() val = Rd(PC.W); front.W = final.W = ZpWord(val); PC.W++;\ - final.W += Y; if(final.B.h != front.B.h) nCPUCycle++; \ - front.W = val; val = Rd(final.W) - -/* second set - gets the ADDRESS that the mode is referring to (for operators - * that write to memory) note that AbsoluteX, AbsoluteY, and - * IndirectY modes do NOT check for page boundary crossing here - * since that extra cycle isn't added for operators that write to - * memory (it only applies to ones that only read from memory.. in - * which case the 1st set should be used) - */ -/*Zero Page*/ -#define Ad_AdZp() final.W = Rd(PC.W); PC.W++ -/*Zero Page, X*/ -#define Ad_AdZx() final.W = front.W = Rd(PC.W); final.B.l += X; PC.W++ -/*Zero Page, Y*/ -#define Ad_AdZy() final.W = front.W = Rd(PC.W); final.B.l += Y; PC.W++ -/*Absolute*/ -#define Ad_AdAb() final.W = RdWord(PC.W); PC.W += 2 -/*Absolute, X*/ -#define Ad_AdAx() front.W = final.W = RdWord(PC.W); PC.W += 2; \ - final.W += X -/*Absolute, Y*/ -#define Ad_AdAy() front.W = final.W = RdWord(PC.W); PC.W += 2; \ - final.W += Y -/*(Indirect, X)*/ -#define Ad_AdIx() front.W = final.W = Rd(PC.W); PC.W++; final.B.l += X; \ - final.W = ZpWord(final.B.l) -/*(Indirect), Y*/ -#define Ad_AdIy() front.W = Rd(PC.W); final.W = ZpWord(front.W) + Y; \ - PC.W++ - -/* third set - reads memory, performs the desired operation on the value, then - * writes back to memory - * used for operators that directly change memory (ASL, INC, DEC, etc) - */ -/*Zero Page*/ -#define MRW_Zp(cmd) Ad_AdZp(); val = Zp(final.W); cmd(val); WrZ(final.W,val) -/*Zero Page, X*/ -#define MRW_Zx(cmd) Ad_AdZx(); val = Zp(final.W); cmd(val); WrZ(final.W,val) -/*Zero Page, Y*/ -#define MRW_Zy(cmd) Ad_AdZy(); val = Zp(final.W); cmd(val); WrZ(final.W,val) -/*Absolute*/ -#define MRW_Ab(cmd) Ad_AdAb(); val = Rd(final.W); cmd(val); Wr(final.W,val) -/*Absolute, X*/ -#define MRW_Ax(cmd) Ad_AdAx(); val = Rd(final.W); cmd(val); Wr(final.W,val) -/*Absolute, Y*/ -#define MRW_Ay(cmd) Ad_AdAy(); val = Rd(final.W); cmd(val); Wr(final.W,val) -/*(Indirect, X)*/ -#define MRW_Ix(cmd) Ad_AdIx(); val = Rd(final.W); cmd(val); Wr(final.W,val) -/*(Indirect), Y*/ -#define MRW_Iy(cmd) Ad_AdIy(); val = Rd(final.W); cmd(val); Wr(final.W,val) - -/* Relative modes are special in that they're only used by branch commands - * this macro handles the jump, and should only be called if the branch - * condition was true if the branch condition was false, the PC must be - * incremented - */ - -#define RelJmp(cond) val = Rd(PC.W); PC.W++; final.W = PC.W + (int8_t)(val);\ - if(cond) {\ - nCPUCycle += ((final.B.h != PC.B.h) ? 2 : 1);\ - PC.W = final.W; } - -/* Status Flags */ - -#define C_FLAG 0x01 /* carry flag */ -#define Z_FLAG 0x02 /* zero flag */ -#define I_FLAG 0x04 /* mask interrupt flag */ -#define D_FLAG 0x08 /* decimal flag (decimal mode is unsupported on - NES) */ -#define B_FLAG 0x10 /* break flag (not really in the status register - It's value in ST is never used. When ST is - put in memory (by an interrupt or PHP), this - flag is set only if BRK was called) - ** also when PHP is called due to a bug */ -#define R_FLAG 0x20 /* reserved flag (not really in the register. - It's value is never used. - Whenever ST is put in memory, - this flag is always set) */ -#define V_FLAG 0x40 /* overflow flag */ -#define N_FLAG 0x80 /* sign flag */ - - -/* Lookup Tables */ - -/* the number of CPU cycles used for each instruction */ -static const uint8_t CPU_Cycles[0x100] ICONST_ATTR_NSF_LARGE_IRAM = { -7,6,0,8,3,3,5,5,3,2,2,2,4,4,6,6, -2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7, -6,6,0,8,3,3,5,5,4,2,2,2,4,4,6,6, -2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7, -6,6,0,8,3,3,5,5,3,2,2,2,3,4,6,6, -2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7, -6,6,0,8,3,3,5,5,4,2,2,2,5,4,6,6, -2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7, -2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4, -2,6,0,6,4,4,4,4,2,5,2,5,5,5,5,5, -2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4, -2,5,0,5,4,4,4,4,2,4,2,4,4,4,4,4, -2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6, -2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7, -2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6, -2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7 }; - -/* the status of the NZ flags for the given value */ -static const uint8_t NZTable[0x100] ICONST_ATTR_NSF_LARGE_IRAM = { -Z_FLAG,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0, -N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG, -N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG, -N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG, -N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG, -N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG, -N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG, -N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG, -N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG, -N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG, -N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG, -N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG, -N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG }; - -/* A quick macro for working with the above table */ -#define UpdateNZ(v) ST = (ST & ~(N_FLAG|Z_FLAG)) | NZTable[v] - - -/* - * Opcodes - * - * These opcodes perform the action with the given value (changing that - * value if necessary). Registers and flags associated with the operation - * are changed accordingly. There are a few exceptions which will be noted - * when they arise - */ - - -/* ADC - Adds the value to the accumulator with carry - Changes: A, NVZC - - Decimal mode not supported on the NES - - Due to a bug, NVZ flags are not altered if the Decimal flag is on - --(taken out)-- */ -#define ADC() \ - tw.W = A + val + (ST & C_FLAG); \ - ST = (ST & (I_FLAG|D_FLAG)) | tw.B.h | NZTable[tw.B.l] | \ - ( (0x80 & ~(A ^ val) & (A ^ tw.B.l)) ? V_FLAG : 0 ); \ - A = tw.B.l - -/* AND - Combines the value with the accumulator using a bitwise AND operation - Changes: A, NZ */ -#define AND() \ - A &= val; \ - UpdateNZ(A) - -/* ASL - Left shifts the value 1 bit. The bit that gets shifted out goes to - the carry flag. - Changes: value, NZC */ -#define ASL(value) \ - tw.W = value << 1; \ - ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | tw.B.h | NZTable[tw.B.l]; \ - value = tw.B.l - -/* BIT - Compares memory with the accumulator with an AND operation, but changes - neither. - The two high bits of memory get transferred to the status reg - Z is set if the AND operation yielded zero, otherwise it's cleared - Changes: NVZ */ -#define BIT() \ - ST = (ST & ~(N_FLAG|V_FLAG|Z_FLAG)) | (val & (N_FLAG|V_FLAG)) | \ - ((A & val) ? 0 : Z_FLAG) - -/* CMP, CPX, CPY - Compares memory with the given register with a subtraction operation. - Flags are set accordingly depending on the result: - Reg < Memory: Z=0, C=0 - Reg = Memory: Z=1, C=1 - Reg > Memory: Z=0, C=1 - N is set according to the result of the subtraction operation - Changes: NZC - - NOTE -- CMP, CPX, CPY all share this same routine, so the desired - register (A, X, or Y respectively) must be given when calling - this macro... as well as the memory to compare it with. */ -#define CMP(reg) \ - tw.W = reg - val; \ - ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | (tw.B.h ? 0 : C_FLAG) | \ - NZTable[tw.B.l] - -/* DEC, DEX, DEY - Decriments a value by one. - Changes: value, NZ */ -#define DEC(value) \ - value--; \ - UpdateNZ(value) - -/* EOR - Combines a value with the accumulator using a bitwise exclusive-OR - operation - Changes: A, NZ */ -#define EOR() \ - A ^= val; \ - UpdateNZ(A) - -/* INC, INX, INY - Incriments a value by one. - Changes: value, NZ */ -#define INC(value) \ - value++; \ - UpdateNZ(value) - -/* LSR - Shifts value one bit to the right. Bit that gets shifted out goes to - the Carry flag. - Changes: value, NZC */ -#define LSR(value) \ - tw.W = value >> 1; \ - ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | NZTable[tw.B.l] | \ - (value & 0x01); \ - value = tw.B.l - -/* ORA - Combines a value with the accumulator using a bitwise inclusive-OR - operation - Changes: A, NZ */ -#define ORA() \ - A |= val; \ - UpdateNZ(A) - -/* ROL - Rotates a value one bit to the left: - C <- 7<-6<-5<-4<-3<-2<-1<-0 <- C - Changes: value, NZC */ -#define ROL(value) \ - tw.W = (value << 1) | (ST & 0x01); \ - ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | NZTable[tw.B.l] | tw.B.h; \ - value = tw.B.l - -/* ROR - Rotates a value one bit to the right: - C -> 7->6->5->4->3->2->1->0 -> C - Changes: value, NZC */ -#define ROR(value) \ - tw.W = (value >> 1) | (ST << 7); \ - ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | NZTable[tw.B.l] | \ - (value & 0x01); \ - value = tw.B.l - -/* SBC - Subtracts a value from the accumulator with borrow (inverted carry) - Changes: A, NVZC - - Decimal mode not supported on the NES - - Due to a bug, NVZ flags are not altered if the Decimal flag is on - --(taken out)-- */ -#define SBC() \ - tw.W = A - val - ((ST & C_FLAG) ? 0 : 1); \ - ST = (ST & (I_FLAG|D_FLAG)) | (tw.B.h ? 0 : C_FLAG) | NZTable[tw.B.l] | \ - (((A ^ val) & (A ^ tw.B.l) & 0x80) ? V_FLAG : 0); \ - A = tw.B.l - -/* Undocumented Opcodes - * - * These opcodes are not included in the official specifications. However, - * some of the unused opcode values perform operations which have since been - * documented. - */ - - -/* ASO - Left shifts a value, then ORs the result with the accumulator - Changes: value, A, NZC */ -#define ASO(value) \ - tw.W = value << 1; \ - A |= tw.B.l; \ - ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | NZTable[A] | tw.B.h; \ - value = tw.B.l - -/* RLA - Roll memory left 1 bit, then AND the result with the accumulator - Changes: value, A, NZC */ -#define RLA(value) \ - tw.W = (value << 1) | (ST & 0x01); \ - A &= tw.B.l; \ - ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | NZTable[A] | tw.B.h; \ - value = tw.B.l - -/* LSE - Right shifts a value one bit, then EORs the result with the accumulator - Changes: value, A, NZC */ -#define LSE(value) \ - tw.W = value >> 1; \ - A ^= tw.B.l; \ - ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | NZTable[A] | (value & 0x01); \ - value = tw.B.l - -/* RRA - Roll memory right one bit, then ADC the result - Changes: value, A, NVZC */ -#define RRA(value) \ - tw.W = (value >> 1) | (ST << 7); \ - ST = (ST & ~C_FLAG) | (value & 0x01); \ - value = tw.B.l; \ - ADC() - -/* AXS - ANDs the contents of the X and A registers and stores the result - int memory. - Changes: value [DOES NOT CHANGE X, A, or any flags] */ -#define AXS(value) \ - value = A & X - -/* DCM - Decriments a value and compares it with the A register. - Changes: value, NZC */ -#define DCM(value) \ - value--; \ - CMP(A) - -/* INS - Incriments a value then SBCs it - Changes: value, A, NVZC */ -#define INS(value) \ - value++; \ - SBC() - -/* AXA */ -#define AXA(value) \ - value = A & X & (Rd(PC.W - 1) + 1) - - -/* The 6502 emulation function! */ - -static uint8_t val; -static uint8_t op; - -static uint32_t Emulate6502(uint32_t runto) -{ - /* If the CPU is jammed... don't bother */ - if(bCPUJammed == 1) - return 0; - - register union TWIN tw; /* used in calculations */ - register uint8_t ST = regP; - register union TWIN PC; - uint8_t SP = regSP; - register uint8_t A = regA; - register uint8_t X = regX; - register uint8_t Y = regY; - union TWIN front; - union TWIN final; - PC.W = regPC; - - uint32_t ret = nCPUCycle; - - ENTER_TIMER(cpu); - - /* Start the loop */ - - while(nCPUCycle < runto) - { - op = Rd(PC.W); - PC.W++; - - nCPUCycle += CPU_Cycles[op]; - switch(op) - { - /* Documented Opcodes first */ - - /* Flag setting/clearing */ - case 0x18: ST &= ~C_FLAG; break; /* CLC */ - case 0x38: ST |= C_FLAG; break; /* SEC */ - case 0x58: ST &= ~I_FLAG; break; /* CLI */ - case 0x78: ST |= I_FLAG; break; /* SEI */ - case 0xB8: ST &= ~V_FLAG; break; /* CLV */ - case 0xD8: ST &= ~D_FLAG; break; /* CLD */ - case 0xF8: ST |= D_FLAG; break; /* SED */ - - /* Branch commands */ - case 0x10: RelJmp(!(ST & N_FLAG)); break; /* BPL */ - case 0x30: RelJmp( (ST & N_FLAG)); break; /* BMI */ - case 0x50: RelJmp(!(ST & V_FLAG)); break; /* BVC */ - case 0x70: RelJmp( (ST & V_FLAG)); break; /* BVS */ - case 0x90: RelJmp(!(ST & C_FLAG)); break; /* BCC */ - case 0xB0: RelJmp( (ST & C_FLAG)); break; /* BCS */ - case 0xD0: RelJmp(!(ST & Z_FLAG)); break; /* BNE */ - case 0xF0: RelJmp( (ST & Z_FLAG)); break; /* BEQ */ - - /* Direct stack alteration commands (push/pull commands) */ - case 0x08: PUSH(ST | R_FLAG | B_FLAG); break; /* PHP */ - case 0x28: PULL(ST); break; /* PLP */ - case 0x48: PUSH(A); break; /* PHA */ - case 0x68: PULL(A); UpdateNZ(A); break; /* PLA */ - - /* Register Transfers */ - case 0x8A: A = X; UpdateNZ(A); break; /* TXA */ - case 0x98: A = Y; UpdateNZ(A); break; /* TYA */ - case 0x9A: SP = X; break; /* TXS */ - case 0xA8: Y = A; UpdateNZ(A); break; /* TAY */ - case 0xAA: X = A; UpdateNZ(A); break; /* TAX */ - case 0xBA: X = SP; UpdateNZ(X); break; /* TSX */ - - /* Other commands */ - - /* ADC */ - case 0x61: Ad_VlIx(); ADC(); break; - case 0x65: Ad_VlZp(); ADC(); break; - case 0x69: Ad_VlIm(); ADC(); break; - case 0x6D: Ad_VlAb(); ADC(); break; - case 0x71: Ad_VlIy(); ADC(); break; - case 0x75: Ad_VlZx(); ADC(); break; - case 0x79: Ad_VlAy(); ADC(); break; - case 0x7D: Ad_VlAx(); ADC(); break; - - /* AND */ - case 0x21: Ad_VlIx(); AND(); break; - case 0x25: Ad_VlZp(); AND(); break; - case 0x29: Ad_VlIm(); AND(); break; - case 0x2D: Ad_VlAb(); AND(); break; - case 0x31: Ad_VlIy(); AND(); break; - case 0x35: Ad_VlZx(); AND(); break; - case 0x39: Ad_VlAy(); AND(); break; - case 0x3D: Ad_VlAx(); AND(); break; - - /* ASL */ - case 0x0A: ASL(A); break; - case 0x06: MRW_Zp(ASL); break; - case 0x0E: MRW_Ab(ASL); break; - case 0x16: MRW_Zx(ASL); break; - case 0x1E: MRW_Ax(ASL); break; - - /* BIT */ - case 0x24: Ad_VlZp(); BIT(); break; - case 0x2C: Ad_VlAb(); BIT(); break; - - /* BRK */ - case 0x00: - if(bIgnoreBRK) - break; - PC.W++; /*BRK has a padding byte*/ - PUSH(PC.B.h); /*push high byte of the return address*/ - PUSH(PC.B.l); /*push low byte of return address*/ - PUSH(ST | R_FLAG | B_FLAG); /*push processor status with R|B flags*/ - ST |= I_FLAG; /*mask interrupts*/ - PC.W = RdWord(0xFFFE); /*read the IRQ vector and jump to it*/ - - /* extra check to make sure we didn't hit an infinite BRK loop */ - if(!Rd(PC.W)) /* next command will be BRK */ - { - /* the CPU will endlessly loop... - just jam it to ease processing power */ - bCPUJammed = 1; - goto jammed; - } - break; - - /* CMP */ - case 0xC1: Ad_VlIx(); CMP(A); break; - case 0xC5: Ad_VlZp(); CMP(A); break; - case 0xC9: Ad_VlIm(); CMP(A); break; - case 0xCD: Ad_VlAb(); CMP(A); break; - case 0xD1: Ad_VlIy(); CMP(A); break; - case 0xD5: Ad_VlZx(); CMP(A); break; - case 0xD9: Ad_VlAy(); CMP(A); break; - case 0xDD: Ad_VlAx(); CMP(A); break; - - /* CPX */ - case 0xE0: Ad_VlIm(); CMP(X); break; - case 0xE4: Ad_VlZp(); CMP(X); break; - case 0xEC: Ad_VlAb(); CMP(X); break; - - /* CPY */ - case 0xC0: Ad_VlIm(); CMP(Y); break; - case 0xC4: Ad_VlZp(); CMP(Y); break; - case 0xCC: Ad_VlAb(); CMP(Y); break; - - /* DEC */ - case 0xCA: DEC(X); break; /* DEX */ - case 0x88: DEC(Y); break; /* DEY */ - case 0xC6: MRW_Zp(DEC); break; - case 0xCE: MRW_Ab(DEC); break; - case 0xD6: MRW_Zx(DEC); break; - case 0xDE: MRW_Ax(DEC); break; - - /* EOR */ - case 0x41: Ad_VlIx(); EOR(); break; - case 0x45: Ad_VlZp(); EOR(); break; - case 0x49: Ad_VlIm(); EOR(); break; - case 0x4D: Ad_VlAb(); EOR(); break; - case 0x51: Ad_VlIy(); EOR(); break; - case 0x55: Ad_VlZx(); EOR(); break; - case 0x59: Ad_VlAy(); EOR(); break; - case 0x5D: Ad_VlAx(); EOR(); break; - - /* INC */ - case 0xE8: INC(X); break; /* INX */ - case 0xC8: INC(Y); break; /* INY */ - case 0xE6: MRW_Zp(INC); break; - case 0xEE: MRW_Ab(INC); break; - case 0xF6: MRW_Zx(INC); break; - case 0xFE: MRW_Ax(INC); break; - - /* JMP */ - /* Absolute JMP */ - case 0x4C: final.W = RdWord(PC.W); PC.W = final.W; val = 0; break; - /* Indirect JMP -- must take caution: - Indirection at 01FF will read from 01FF and 0100 (not 0200) */ - case 0x6C: front.W = final.W = RdWord(PC.W); - PC.B.l = Rd(final.W); final.B.l++; - PC.B.h = Rd(final.W); final.W = PC.W; - break; - /* JSR */ - case 0x20: - val = 0; - final.W = RdWord(PC.W); - PC.W++; /* JSR only increments the return address by one. - It's incremented again upon RTS */ - PUSH(PC.B.h); /* push high byte of return address */ - PUSH(PC.B.l); /* push low byte of return address */ - PC.W = final.W; - break; - - /* LDA */ - case 0xA1: Ad_VlIx(); A = val; UpdateNZ(A); break; - case 0xA5: Ad_VlZp(); A = val; UpdateNZ(A); break; - case 0xA9: Ad_VlIm(); A = val; UpdateNZ(A); break; - case 0xAD: Ad_VlAb(); A = val; UpdateNZ(A); break; - case 0xB1: Ad_VlIy(); A = val; UpdateNZ(A); break; - case 0xB5: Ad_VlZx(); A = val; UpdateNZ(A); break; - case 0xB9: Ad_VlAy(); A = val; UpdateNZ(A); break; - case 0xBD: Ad_VlAx(); A = val; UpdateNZ(A); break; - - /* LDX */ - case 0xA2: Ad_VlIm(); X = val; UpdateNZ(X); break; - case 0xA6: Ad_VlZp(); X = val; UpdateNZ(X); break; - case 0xAE: Ad_VlAb(); X = val; UpdateNZ(X); break; - case 0xB6: Ad_VlZy(); X = val; UpdateNZ(X); break; - case 0xBE: Ad_VlAy(); X = val; UpdateNZ(X); break; - - /* LDY */ - case 0xA0: Ad_VlIm(); Y = val; UpdateNZ(Y); break; - case 0xA4: Ad_VlZp(); Y = val; UpdateNZ(Y); break; - case 0xAC: Ad_VlAb(); Y = val; UpdateNZ(Y); break; - case 0xB4: Ad_VlZx(); Y = val; UpdateNZ(Y); break; - case 0xBC: Ad_VlAx(); Y = val; UpdateNZ(Y); break; - - /* LSR */ - case 0x4A: LSR(A); break; - case 0x46: MRW_Zp(LSR); break; - case 0x4E: MRW_Ab(LSR); break; - case 0x56: MRW_Zx(LSR); break; - case 0x5E: MRW_Ax(LSR); break; - - /* NOP */ - case 0xEA: - - /* --- Undocumented --- - These opcodes perform the same action as NOP */ - case 0x1A: case 0x3A: case 0x5A: - case 0x7A: case 0xDA: case 0xFA: break; - - /* ORA */ - case 0x01: Ad_VlIx(); ORA(); break; - case 0x05: Ad_VlZp(); ORA(); break; - case 0x09: Ad_VlIm(); ORA(); break; - case 0x0D: Ad_VlAb(); ORA(); break; - case 0x11: Ad_VlIy(); ORA(); break; - case 0x15: Ad_VlZx(); ORA(); break; - case 0x19: Ad_VlAy(); ORA(); break; - case 0x1D: Ad_VlAx(); ORA(); break; - - /* ROL */ - case 0x2A: ROL(A); break; - case 0x26: MRW_Zp(ROL); break; - case 0x2E: MRW_Ab(ROL); break; - case 0x36: MRW_Zx(ROL); break; - case 0x3E: MRW_Ax(ROL); break; - - /* ROR */ - case 0x6A: ROR(A); break; - case 0x66: MRW_Zp(ROR); break; - case 0x6E: MRW_Ab(ROR); break; - case 0x76: MRW_Zx(ROR); break; - case 0x7E: MRW_Ax(ROR); break; - - /* RTI */ - case 0x40: - PULL(ST); /*pull processor status*/ - PULL(PC.B.l); /*pull low byte of return address*/ - PULL(PC.B.h); /*pull high byte of return address*/ - break; - - /* RTS */ - case 0x60: - PULL(PC.B.l); - PULL(PC.B.h); - PC.W++; /* the return address is one less of what it needs */ - break; - - /* SBC */ - case 0xE1: Ad_VlIx(); SBC(); break; - case 0xE5: Ad_VlZp(); SBC(); break; - /* - Undocumented - EB performs the same operation as SBC immediate */ - case 0xEB: - case 0xE9: Ad_VlIm(); SBC(); break; - case 0xED: Ad_VlAb(); SBC(); break; - case 0xF1: Ad_VlIy(); SBC(); break; - case 0xF5: Ad_VlZx(); SBC(); break; - case 0xF9: Ad_VlAy(); SBC(); break; - case 0xFD: Ad_VlAx(); SBC(); break; - - /* STA */ - case 0x81: Ad_AdIx(); val = A; Wr(final.W,A); break; - case 0x85: Ad_AdZp(); val = A; WrZ(final.W,A); break; - case 0x8D: Ad_AdAb(); val = A; Wr(final.W,A); break; - case 0x91: Ad_AdIy(); val = A; Wr(final.W,A); break; - case 0x95: Ad_AdZx(); val = A; WrZ(final.W,A); break; - case 0x99: Ad_AdAy(); val = A; Wr(final.W,A); break; - case 0x9D: Ad_AdAx(); val = A; Wr(final.W,A); break; - - /* STX */ - case 0x86: Ad_AdZp(); val = X; WrZ(final.W,X); break; - case 0x8E: Ad_AdAb(); val = X; Wr(final.W,X); break; - case 0x96: Ad_AdZy(); val = X; WrZ(final.W,X); break; - - /* STY */ - case 0x84: Ad_AdZp(); val = Y; WrZ(final.W,Y); break; - case 0x8C: Ad_AdAb(); val = Y; Wr(final.W,Y); break; - case 0x94: Ad_AdZx(); val = Y; WrZ(final.W,Y); break; - - /* Undocumented Opcodes */ - /* ASO */ - case 0x03: if(bIgnoreIllegalOps) break; MRW_Ix(ASO); break; - case 0x07: if(bIgnoreIllegalOps) break; MRW_Zp(ASO); break; - case 0x0F: if(bIgnoreIllegalOps) break; MRW_Ab(ASO); break; - case 0x13: if(bIgnoreIllegalOps) break; MRW_Iy(ASO); break; - case 0x17: if(bIgnoreIllegalOps) break; MRW_Zx(ASO); break; - case 0x1B: if(bIgnoreIllegalOps) break; MRW_Ay(ASO); break; - case 0x1F: if(bIgnoreIllegalOps) break; MRW_Ax(ASO); break; - - /* RLA */ - case 0x23: if(bIgnoreIllegalOps) break; MRW_Ix(RLA); break; - case 0x27: if(bIgnoreIllegalOps) break; MRW_Zp(RLA); break; - case 0x2F: if(bIgnoreIllegalOps) break; MRW_Ab(RLA); break; - case 0x33: if(bIgnoreIllegalOps) break; MRW_Iy(RLA); break; - case 0x37: if(bIgnoreIllegalOps) break; MRW_Zx(RLA); break; - case 0x3B: if(bIgnoreIllegalOps) break; MRW_Ay(RLA); break; - case 0x3F: if(bIgnoreIllegalOps) break; MRW_Ax(RLA); break; - - /* LSE */ - case 0x43: if(bIgnoreIllegalOps) break; MRW_Ix(LSE); break; - case 0x47: if(bIgnoreIllegalOps) break; MRW_Zp(LSE); break; - case 0x4F: if(bIgnoreIllegalOps) break; MRW_Ab(LSE); break; - case 0x53: if(bIgnoreIllegalOps) break; MRW_Iy(LSE); break; - case 0x57: if(bIgnoreIllegalOps) break; MRW_Zx(LSE); break; - case 0x5B: if(bIgnoreIllegalOps) break; MRW_Ay(LSE); break; - case 0x5F: if(bIgnoreIllegalOps) break; MRW_Ax(LSE); break; - - /* RRA */ - case 0x63: if(bIgnoreIllegalOps) break; MRW_Ix(RRA); break; - case 0x67: if(bIgnoreIllegalOps) break; MRW_Zp(RRA); break; - case 0x6F: if(bIgnoreIllegalOps) break; MRW_Ab(RRA); break; - case 0x73: if(bIgnoreIllegalOps) break; MRW_Iy(RRA); break; - case 0x77: if(bIgnoreIllegalOps) break; MRW_Zx(RRA); break; - case 0x7B: if(bIgnoreIllegalOps) break; MRW_Ay(RRA); break; - case 0x7F: if(bIgnoreIllegalOps) break; MRW_Ax(RRA); break; - - /* AXS */ - case 0x83: if(bIgnoreIllegalOps) break; MRW_Ix(AXS); break; - case 0x87: if(bIgnoreIllegalOps) break; MRW_Zp(AXS); break; - case 0x8F: if(bIgnoreIllegalOps) break; MRW_Ab(AXS); break; - case 0x97: if(bIgnoreIllegalOps) break; MRW_Zy(AXS); break; - - /* LAX */ - case 0xA3: if(bIgnoreIllegalOps) break; - Ad_VlIx(); X = A = val; UpdateNZ(A); break; - case 0xA7: if(bIgnoreIllegalOps) break; - Ad_VlZp(); X = A = val; UpdateNZ(A); break; - case 0xAF: if(bIgnoreIllegalOps) break; - Ad_VlAb(); X = A = val; UpdateNZ(A); break; - case 0xB3: if(bIgnoreIllegalOps) break; - Ad_VlIy(); X = A = val; UpdateNZ(A); break; - case 0xB7: if(bIgnoreIllegalOps) break; - Ad_VlZy(); X = A = val; UpdateNZ(A); break; - case 0xBF: if(bIgnoreIllegalOps) break; - Ad_VlAy(); X = A = val; UpdateNZ(A); break; - - /* DCM */ - case 0xC3: if(bIgnoreIllegalOps) break; MRW_Ix(DCM); break; - case 0xC7: if(bIgnoreIllegalOps) break; MRW_Zp(DCM); break; - case 0xCF: if(bIgnoreIllegalOps) break; MRW_Ab(DCM); break; - case 0xD3: if(bIgnoreIllegalOps) break; MRW_Iy(DCM); break; - case 0xD7: if(bIgnoreIllegalOps) break; MRW_Zx(DCM); break; - case 0xDB: if(bIgnoreIllegalOps) break; MRW_Ay(DCM); break; - case 0xDF: if(bIgnoreIllegalOps) break; MRW_Ax(DCM); break; - - /* INS */ - case 0xE3: if(bIgnoreIllegalOps) break; MRW_Ix(INS); break; - case 0xE7: if(bIgnoreIllegalOps) break; MRW_Zp(INS); break; - case 0xEF: if(bIgnoreIllegalOps) break; MRW_Ab(INS); break; - case 0xF3: if(bIgnoreIllegalOps) break; MRW_Iy(INS); break; - case 0xF7: if(bIgnoreIllegalOps) break; MRW_Zx(INS); break; - case 0xFB: if(bIgnoreIllegalOps) break; MRW_Ay(INS); break; - case 0xFF: if(bIgnoreIllegalOps) break; MRW_Ax(INS); break; - - /* ALR - AND Accumulator with memory and LSR the result */ - case 0x4B: if(bIgnoreIllegalOps) break; - Ad_VlIm(); A &= val; LSR(A); break; - - /* ARR - ANDs memory with the Accumulator and RORs the result */ - case 0x6B: if(bIgnoreIllegalOps) break; - Ad_VlIm(); A &= val; ROR(A); break; - - /* XAA - Transfers X -> A, then ANDs A with memory */ - case 0x8B: if(bIgnoreIllegalOps) break; - Ad_VlIm(); A = X & val; UpdateNZ(A); break; - - /* OAL - OR the Accumulator with #EE, AND Accumulator with Memory, - Transfer A -> X */ - case 0xAB: if(bIgnoreIllegalOps) break; - Ad_VlIm(); X = (A &= (val | 0xEE)); - UpdateNZ(A); break; - - /* SAX - ANDs A and X registers (does not change A), subtracts memory - from result (CMP style, not SBC style) result is stored in X */ - case 0xCB: if(bIgnoreIllegalOps) break; - Ad_VlIm(); tw.W = (X & A) - val; X = tw.B.l; - ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | NZTable[X] | - (tw.B.h ? C_FLAG : 0); break; - /* SKB - Skip Byte... or DOP - Double No-Op - These bytes do nothing, but take a parameter (which can be - ignored) */ - case 0x04: case 0x14: case 0x34: case 0x44: case 0x54: case 0x64: - case 0x80: case 0x82: case 0x89: case 0xC2: case 0xD4: case 0xE2: - case 0xF4: - if(bIgnoreIllegalOps) break; - PC.W++; /* skip unused byte */ - break; - - /* SKW - Swip Word... or TOP - Tripple No-Op - These bytes are the same as SKB, only they take a 2 byte parameter. - This can be ignored in some cases, but the read needs to be - performed in a some cases because an extra clock cycle may be used - in the process */ - /* Absolute address... no need for operator */ - case 0x0C: - if(bIgnoreIllegalOps) break; - PC.W += 2; break; - /* Absolute X address... may cross page, have to perform the read */ - case 0x1C: case 0x3C: case 0x5C: case 0x7C: case 0xDC: case 0xFC: - if(bIgnoreIllegalOps) break; - Ad_VlAx(); break; - - /* HLT / JAM - Jams up CPU operation */ - case 0x02: case 0x12: case 0x22: case 0x32: case 0x42: case 0x52: - case 0x62: case 0x72: case 0x92: case 0xB2: case 0xD2: case 0xF2: - /*it's not -really- jammed... only the NSF code has ended*/ - if(PC.W == 0x5004) bCPUJammed = 2; - else - { - if(bIgnoreIllegalOps) break; - bCPUJammed = 1; - } - goto jammed; - - /* TAS */ - case 0x9B: - if(bIgnoreIllegalOps) break; - Ad_AdAy(); - SP = A & X & (Rd(PC.W - 1) + 1); - Wr(final.W,SP); - break; - - /* SAY */ - case 0x9C: - if(bIgnoreIllegalOps) break; - Ad_AdAx(); - Y &= (Rd(PC.W - 1) + 1); - Wr(final.W,Y); - break; - - /* XAS */ - case 0x9E: - if(bIgnoreIllegalOps) break; - Ad_AdAy(); - X &= (Rd(PC.W - 1) + 1); - Wr(final.W,X); - break; - - /* AXA */ - case 0x93: if(bIgnoreIllegalOps) break; MRW_Iy(AXA); break; - case 0x9F: if(bIgnoreIllegalOps) break; MRW_Ay(AXA); break; - - /* ANC */ - case 0x0B: case 0x2B: - if(bIgnoreIllegalOps) break; - Ad_VlIm(); - A &= val; - ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | - NZTable[A] | ((A & 0x80) ? C_FLAG : 0); - break; +CODEC_HEADER - /* LAS */ - case 0xBB: - if(bIgnoreIllegalOps) break; - Ad_VlAy(); - X = A = (SP &= val); - UpdateNZ(A); - break; - } - } +/* Maximum number of bytes to process in one iteration */ +#define CHUNK_SIZE (1024*2) -jammed: - regPC = PC.W; - regA = A; - regX = X; - regY = Y; - regSP = SP; - regP = ST; - - EXIT_TIMER(cpu); - - return (nCPUCycle - ret); -} +static int16_t samples[CHUNK_SIZE] IBSS_ATTR; +static struct Nsf_Emu nsf_emu IDATA_ATTR CACHEALIGN_ATTR; /****************** rockbox interface ******************/ -/** Operational info **/ -static int track = 0; -static char last_path[MAX_PATH]; -static int dontresettrack = 0; -static bool repeat_one = false; - -static void set_codec_track(int t, int d) { - int track,fade,def=0; - SetTrack(t); +static void set_codec_track(int t, int multitrack) { + Nsf_start_track(&nsf_emu, t); /* for REPEAT_ONE we disable track limits */ - if (!repeat_one) { - if (!bIsExtended || nTrackTime[t]==-1) {track=60*2*1000; def=1;} - else track=nTrackTime[t]; - if (!bIsExtended || nTrackFade[t]==-1) fade=5*1000; - else fade=nTrackFade[t]; - nSilenceTrackMS=5000; - SetFadeTime(track,track+fade, fNSFPlaybackSpeed,def); + if (ci->global_settings->repeat_mode != REPEAT_ONE) { + Track_set_fade(&nsf_emu, Track_length( &nsf_emu, t ) - 4000, 4000); } - ci->set_elapsed(d*1000); /* d is track no to display */ + if (multitrack) ci->set_elapsed(t*1000); /* t is track no to display */ + else ci->set_elapsed(0); } /* this is the codec entry point */ enum codec_status codec_main(enum codec_entry_call_reason reason) { if (reason == CODEC_LOAD) { - /* we only render 16 bits, 44.1KHz, Mono */ + /* we only render 16 bits */ ci->configure(DSP_SET_SAMPLE_DEPTH, 16); + + /* 44 Khz, Interleaved stereo */ ci->configure(DSP_SET_FREQUENCY, 44100); - ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO); + ci->configure(DSP_SET_STEREO_MODE, STEREO_INTERLEAVED); - RebuildOutputTables(); + Nsf_init(&nsf_emu); + Nsf_set_sample_rate(&nsf_emu, 44100); } return CODEC_OK; } - + /* this is called for each file to process */ enum codec_status codec_run(void) { - int written; + blargg_err_t err; uint8_t *buf; size_t n; - int endofstream; /* end of stream flag */ - int usingplaylist = 0; + int track, is_multitrack; + uint32_t elapsed_time; intptr_t param; - + + track = is_multitrack = 0; + elapsed_time = 0; + DEBUGF("NSF: next_track\n"); if (codec_init()) { return CODEC_ERROR; - } - DEBUGF("NSF: after init\n"); - + } + codec_set_replaygain(ci->id3); /* Read the entire file */ @@ -4383,100 +73,63 @@ enum codec_status codec_run(void) DEBUGF("NSF: file load failed\n"); return CODEC_ERROR; } + + if ((err = Nsf_load(&nsf_emu, buf, ci->filesize))) { + DEBUGF("NSF: Nsf_load failed (%s)\n", err); + return CODEC_ERROR; + } - repeat_one = ci->global_settings->repeat_mode == REPEAT_ONE; - -init_nsf: - if(!NSFCore_Initialize()) { - DEBUGF("NSF: NSFCore_Initialize failed\n"); return CODEC_ERROR;} + /* Update internal track count */ + if (nsf_emu.m3u.size > 0) + nsf_emu.track_count = nsf_emu.m3u.size; - if(LoadFile(buf,ci->filesize)) { - DEBUGF("NSF: LoadFile failed\n"); return CODEC_ERROR;} - if(!SetPlaybackOptions(44100)) { - DEBUGF("NSF: SetPlaybackOptions failed\n"); return CODEC_ERROR;} - if(!LoadNSF(nDataBufferSize)) { - DEBUGF("NSF: LoadNSF failed\n"); return CODEC_ERROR;} + if (nsf_emu.track_count > 1) is_multitrack = 1; - if (!dontresettrack||strcmp(ci->id3->path,last_path)) { - /* if this is the first time we're seeing this file, or if we haven't - been asked to preserve the track number, default to the proper - initial track */ - if (bIsExtended && !repeat_one && nPlaylistSize>0) { - /* decide to use the playlist */ - usingplaylist=1; - track=0; - set_codec_track(nPlaylist[0],0); - } else { - /* simply use the initial track */ - track=nInitialTrack; - set_codec_track(track,track); - } - } else { - /* if we've already been running this file assume track is set - already */ - if (usingplaylist) set_codec_track(nPlaylist[track],track); - else set_codec_track(track,track); - } - strcpy(last_path,ci->id3->path); +next_track: + set_codec_track(track, is_multitrack); /* The main decoder loop */ - - endofstream = 0; - - reset_profile_timers(); - - while (!endofstream) { + while (1) { enum codec_command_action action = ci->get_command(¶m); if (action == CODEC_ACTION_HALT) break; if (action == CODEC_ACTION_SEEK_TIME) { - track=param/1000; - if (usingplaylist) { - if (track>=nPlaylistSize) break; - } else { - if (track>=nTrackCount) break; + if (is_multitrack) { + track = param/1000; + ci->seek_complete(); + if (track >= nsf_emu.track_count) break; + goto next_track; } - dontresettrack=1; + + ci->set_elapsed(param); + elapsed_time = param; + Track_seek(&nsf_emu, param); ci->seek_complete(); - goto init_nsf; + + /* Set fade again */ + if (ci->global_settings->repeat_mode != REPEAT_ONE) { + Track_set_fade(&nsf_emu, Track_length( &nsf_emu, track ), 4000); + } } - ENTER_TIMER(total); - written=GetSamples((uint8_t*)samples,WAV_CHUNK_SIZE/2); - EXIT_TIMER(total); - - if (!written || SongCompleted()) { - print_timers(last_path,track); - reset_profile_timers(); - + /* Generate audio buffer */ + err = Nsf_play(&nsf_emu, CHUNK_SIZE, samples); + if (err || nsf_emu.track_ended) { track++; - if (usingplaylist) { - if (track>=nPlaylistSize) break; - } else { - if (track>=nTrackCount) break; - } - dontresettrack=1; - goto init_nsf; + if (track >= nsf_emu.track_count) break; + goto next_track; } - ci->pcmbuf_insert(samples, NULL, written >> 1); - } - - print_timers(last_path,track); + ci->pcmbuf_insert(samples, NULL, CHUNK_SIZE >> 1); - if (repeat_one) { - /* in repeat one mode just advance to the next track */ - track++; - if (track>=nTrackCount) track=0; - dontresettrack=1; - /* at this point we can't tell if another file has been selected */ - } else { - /* otherwise do a proper load of the next file */ - dontresettrack=0; - last_path[0]='\0'; + /* Set elapsed time for one track files */ + if (is_multitrack == 0) { + elapsed_time += (CHUNK_SIZE / 2) / 44.1; + ci->set_elapsed(elapsed_time); + } } - + return CODEC_OK; } diff --git a/apps/codecs/sgc.c b/apps/codecs/sgc.c new file mode 100644 index 0000000000..e5f0299980 --- /dev/null +++ b/apps/codecs/sgc.c @@ -0,0 +1,123 @@ + +/* Ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */ + +#include +#include "libgme/sgc_emu.h" + +CODEC_HEADER + +/* Maximum number of bytes to process in one iteration */ +#define CHUNK_SIZE (1024*2) + +static int16_t samples[CHUNK_SIZE] IBSS_ATTR; +static struct Sgc_Emu sgc_emu IDATA_ATTR CACHEALIGN_ATTR; + +/* Coleco Bios */ +/* Colecovision not supported yet +static char coleco_bios[0x2000]; +*/ + +/****************** rockbox interface ******************/ + +static void set_codec_track(int t) { + Sgc_start_track(&sgc_emu, t); + + /* for REPEAT_ONE we disable track limits */ + if (ci->global_settings->repeat_mode != REPEAT_ONE) { + Track_set_fade(&sgc_emu, Track_get_length( &sgc_emu, t ), 4000); + } + ci->set_elapsed(t*1000); /* t is track no to display */ +} + +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* we only render 16 bits */ + ci->configure(DSP_SET_SAMPLE_DEPTH, 16); + + /* 44 Khz, Interleaved stereo */ + ci->configure(DSP_SET_FREQUENCY, 44100); + ci->configure(DSP_SET_STEREO_MODE, STEREO_INTERLEAVED); + + Sgc_init(&sgc_emu); + Sgc_set_sample_rate(&sgc_emu, 44100); + + /* set coleco bios, should be named coleco_bios.rom */ + /* Colecovision not supported yet + int fd = ci->open("/coleco_bios.rom", O_RDONLY); + if ( fd >= 0 ) { + ci->read(fd, coleco_bios, 0x2000); + ci->close(fd); + set_coleco_bios( &sgc_emu, coleco_bios ); + } + */ + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ + blargg_err_t err; + uint8_t *buf; + size_t n; + intptr_t param; + int track = 0; + + DEBUGF("SGC: next_track\n"); + if (codec_init()) { + return CODEC_ERROR; + } + + codec_set_replaygain(ci->id3); + + /* Read the entire file */ + DEBUGF("SGC: request file\n"); + ci->seek_buffer(0); + buf = ci->request_buffer(&n, ci->filesize); + if (!buf || n < (size_t)ci->filesize) { + DEBUGF("SGC: file load failed\n"); + return CODEC_ERROR; + } + + if ((err = Sgc_load_mem(&sgc_emu, buf, ci->filesize))) { + DEBUGF("SGC: Sgc_load failed (%s)\n", err); + return CODEC_ERROR; + } + + /* Update internal track count */ + if (sgc_emu.m3u.size > 0) + sgc_emu.track_count = sgc_emu.m3u.size; + +next_track: + set_codec_track(track); + + /* The main decoder loop */ + while (1) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) + break; + + if (action == CODEC_ACTION_SEEK_TIME) { + track = param/1000; + ci->seek_complete(); + if (track >= sgc_emu.track_count) break; + goto next_track; + } + + /* Generate audio buffer */ + err = Sgc_play(&sgc_emu, CHUNK_SIZE, samples); + if (err || sgc_emu.track_ended) { + track++; + if (track >= sgc_emu.track_count) break; + goto next_track; + } + + ci->pcmbuf_insert(samples, NULL, CHUNK_SIZE >> 1); + } + + return CODEC_OK; +} diff --git a/apps/codecs/vgm.c b/apps/codecs/vgm.c new file mode 100644 index 0000000000..89bfd1937b --- /dev/null +++ b/apps/codecs/vgm.c @@ -0,0 +1,142 @@ + +/* Ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */ +/* Inflate code taken from WikiViewer plugin by Adam Gashlin */ + +#include + +#include "libgme/blargg_endian.h" +#include "libgme/vgm_emu.h" +#include "libgme/inflate/mallocer.h" +#include "libgme/inflate/inflate.h" + +CODEC_HEADER + +/* Maximum number of bytes to process in one iteration */ +#define CHUNK_SIZE (1024*4) +#define MAINMEMBUF 0 + +static int16_t samples[CHUNK_SIZE] IBSS_ATTR; +static struct Vgm_Emu vgm_emu IDATA_ATTR CACHEALIGN_ATTR; + +static void *inflatebuf; /* heap for gunzip */ +static char *songbuf; /* destination for uncompressed song */ +static uint32_t songbuflen=0; /* size of the song buffer */ +static uint32_t songlen=0; /* used size of the song buffer */ + +/****************** rockbox interface ******************/ + +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* we only render 16 bits */ + ci->configure(DSP_SET_SAMPLE_DEPTH, 16); + + /* 32 Khz, Interleaved stereo */ + ci->configure(DSP_SET_FREQUENCY, 44100); + ci->configure(DSP_SET_STEREO_MODE, STEREO_INTERLEAVED); + + Vgm_init(&vgm_emu); + Vgm_set_sample_rate(&vgm_emu, 44100); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ + blargg_err_t err; + uint8_t *buf; + size_t n; + intptr_t param; + + uint32_t elapsed_time = 0; + + DEBUGF("VGM: next_track\n"); + if (codec_init()) { + return CODEC_ERROR; + } + + codec_set_replaygain(ci->id3); + + /* Read the entire file */ + DEBUGF("VGM: request file\n"); + ci->seek_buffer(0); + buf = ci->request_buffer(&n, ci->filesize); + if (!buf) { + DEBUGF("VGM: file load failed\n"); + return CODEC_ERROR; + } + + /* If couldn't get the whole buffer + will trim file and put and 'end_command' + at the end*/ + if (n < (size_t)ci->filesize) { + DEBUGF("VGM: file was trimmed\n"); + } + + /* If is gzipped decompress it */ + if ( get_le16( buf ) == 0x8b1f ) { + wpw_init_mempool(MAINMEMBUF); + inflatebuf=wpw_malloc(MAINMEMBUF,0x13500); + + /* Will use available remaining memory + as output buffer */ + songbuflen=wpw_available(MAINMEMBUF); + songbuf=wpw_malloc(MAINMEMBUF,songbuflen); + + songlen=decompress(buf,n,songbuf,songbuflen,0,inflatebuf); + + if ((err = Vgm_load_mem(&vgm_emu, songbuf, songlen, true))) { + DEBUGF("VGM: Vgm_load_mem failed (%s)\n", err); + return CODEC_ERROR; + } + + /* Since metadata parser doesn't support VGZ + will set song length here */ + ci->id3->length = Track_get_length( &vgm_emu ); + } + else if ((err = Vgm_load_mem(&vgm_emu, buf, n, false))) { + DEBUGF("VGM: Vgm_load failed_mem (%s)\n", err); + return CODEC_ERROR; + } + + Vgm_start_track(&vgm_emu); + + /* for REPEAT_ONE we disable track limits */ + if (ci->global_settings->repeat_mode != REPEAT_ONE) { + Track_set_fade(&vgm_emu, ci->id3->length - 4000, 4000); + } + + ci->set_elapsed(0); + + /* The main decoder loop */ + while (1) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) + break; + + if (action == CODEC_ACTION_SEEK_TIME) { + ci->set_elapsed(param); + elapsed_time = param; + Track_seek(&vgm_emu, param); + ci->seek_complete(); + + /* Set fade again in case we seek to start of song */ + Track_set_fade(&vgm_emu, ci->id3->length - 4000, 4000); + } + + /* Generate audio buffer */ + err = Vgm_play(&vgm_emu, CHUNK_SIZE, samples); + if (err || vgm_emu.track_ended) break; + + ci->pcmbuf_insert(samples, NULL, CHUNK_SIZE >> 1); + + elapsed_time += (CHUNK_SIZE / 2) / 44.1; + ci->set_elapsed(elapsed_time); + } + + return CODEC_OK; +} diff --git a/apps/filetypes.c b/apps/filetypes.c index 779337e0ca..17a16db4ec 100644 --- a/apps/filetypes.c +++ b/apps/filetypes.c @@ -112,6 +112,13 @@ static const struct filetype inbuilt_filetypes[] = { { "vox", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, { "w64", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, { "tta", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, + { "ay", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, + { "gbs", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, + { "hes", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, + { "sgc", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, + { "vgm", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, + { "vgz", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, + { "kss", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, #endif { "m3u", FILE_ATTR_M3U, Icon_Playlist, LANG_PLAYLIST }, { "m3u8",FILE_ATTR_M3U, Icon_Playlist, LANG_PLAYLIST }, diff --git a/apps/metadata.c b/apps/metadata.c index cbb5b42795..8b1101eaa5 100644 --- a/apps/metadata.c +++ b/apps/metadata.c @@ -216,6 +216,24 @@ const struct afmt_entry audio_formats[AFMT_NUM_CODECS] = /* Advanced Audio Coding High Efficiency in M4A container */ [AFMT_MP4_AAC_HE] = AFMT_ENTRY("AAC-HE","aac", NULL, get_mp4_metadata, "mp4\0"), + /* AY (ZX Spectrum, Amstrad CPC Sound Format) */ + [AFMT_AY] = + AFMT_ENTRY("AY", "ay", NULL, get_ay_metadata, "ay\0"), + /* GBS (Game Boy Sound Format) */ + [AFMT_GBS] = + AFMT_ENTRY("GBS", "gbs", NULL, get_gbs_metadata, "gbs\0"), + /* HES (Hudson Entertainment System Sound Format) */ + [AFMT_HES] = + AFMT_ENTRY("HES", "hes", NULL, get_hes_metadata, "hes\0"), + /* SGC (Sega Master System, Game Gear, Coleco Vision Sound Format) */ + [AFMT_SGC] = + AFMT_ENTRY("SGC", "sgc", NULL, get_sgc_metadata, "sgc\0"), + /* VGM (Video Game Music Format) */ + [AFMT_VGM] = + AFMT_ENTRY("VGM", "vgm", NULL, get_vgm_metadata, "vgm\0vgz\0"), + /* KSS (MSX computer KSS Music File) */ + [AFMT_KSS] = + AFMT_ENTRY("KSS", "kss", NULL, get_kss_metadata, "kss\0"), #endif }; @@ -299,6 +317,12 @@ enum data_type get_audio_base_data_type(int afmt) case AFMT_SID: case AFMT_MOD: case AFMT_SAP: + case AFMT_AY: + case AFMT_GBS: + case AFMT_HES: + case AFMT_SGC: + case AFMT_VGM: + case AFMT_KSS: /* Type must be allocated and loaded in its entirety onto the buffer */ return TYPE_ATOMIC_AUDIO; diff --git a/apps/metadata.h b/apps/metadata.h index d016359d2a..3c5efb7532 100644 --- a/apps/metadata.h +++ b/apps/metadata.h @@ -87,6 +87,12 @@ enum AFMT_WMAVOICE, /* WMA Voice in ASF */ AFMT_MPC_SV8, /* Musepack SV8 */ AFMT_MP4_AAC_HE, /* Advanced Audio Coding (AAC-HE) in M4A container */ + AFMT_AY, /* AY (ZX Spectrum, Amstrad CPC Sound Format) */ + AFMT_GBS, /* GBS (Game Boy Sound Format) */ + AFMT_HES, /* HES (Hudson Entertainment System Sound Format) */ + AFMT_SGC, /* SGC (Sega Master System, Game Gear, Coleco Vision Sound Format) */ + AFMT_VGM, /* VGM (Video Game Music Format) */ + AFMT_KSS, /* KSS (MSX computer KSS Music File) */ #endif /* add new formats at any index above this line to have a sensible order - diff --git a/apps/metadata/ay.c b/apps/metadata/ay.c new file mode 100644 index 0000000000..8b737a7eba --- /dev/null +++ b/apps/metadata/ay.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" + +/* Taken from blargg's Game_Music_Emu library */ + +typedef unsigned char byte; + +/* AY file header */ +enum { header_size = 0x14 }; +struct header_t +{ + byte tag[8]; + byte vers; + byte player; + byte unused[2]; + byte author[2]; + byte comment[2]; + byte max_track; + byte first_track; + byte track_info[2]; +}; + +struct file_t { + struct header_t const* header; + byte const* tracks; + byte const* end; /* end of file data */ +}; + +static int get_be16( const void *a ) +{ + return get_short_be( (void*) a ); +} + +/* Given pointer to 2-byte offset of data, returns pointer to data, or NULL if + * offset is 0 or there is less than min_size bytes of data available. */ +static byte const* get_data( struct file_t const* file, byte const ptr [], int min_size ) +{ + int offset = (int16_t) get_be16( ptr ); + int pos = ptr - (byte const*) file->header; + int size = file->end - (byte const*) file->header; + int limit = size - min_size; + if ( limit < 0 || !offset || (unsigned) (pos + offset) > (unsigned) limit ) + return NULL; + return ptr + offset; +} + +static const char *parse_header( byte const in [], int size, struct file_t* out ) +{ + if ( size < header_size ) + return "wrong file type"; + + out->header = (struct header_t const*) in; + out->end = in + size; + struct header_t const* h = (struct header_t const*) in; + if ( memcmp( h->tag, "ZXAYEMUL", 8 ) ) + return "wrong file type"; + + out->tracks = get_data( out, h->track_info, (h->max_track + 1) * 4 ); + if ( !out->tracks ) + return "missing track data"; + + return 0; +} + +static void copy_ay_fields( struct file_t const* file, struct mp3entry* id3, int track ) +{ + int track_count = file->header->max_track + 1; + + /* calculate track length based on number of subtracks */ + if (track_count > 1) { + id3->length = file->header->max_track * 1000; + } else { + byte const* track_info = get_data( file, file->tracks + track * 4 + 2, 6 ); + if (track_info) + id3->length = get_be16( track_info + 4 ) * (1000 / 50); /* frames to msec */ + else id3->length = 120 * 1000; + } + + if ( id3->length <= 0 ) + id3->length = 120 * 1000; /* 2 minutes */ + + /* If meta info was found in the m3u skip next step */ + if (id3->title && id3->title[0]) return; + + /* If file has more than one track will + use file name as title */ + char * tmp; + if (track_count <= 1) { + tmp = (char *) get_data( file, file->tracks + track * 4, 1 ); + if ( tmp ) id3->title = tmp; + } + + /* Author */ + tmp = (char *) get_data( file, file->header->author, 1 ); + if (tmp) id3->artist = tmp; + + /* Comment */ + tmp = (char *) get_data( file, file->header->comment, 1 ); + if (tmp) id3->comment = tmp; +} + +bool parse_ay_header(int fd, struct mp3entry *id3) +{ + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->id3v2buf; + struct file_t file; + int read_bytes; + + lseek(fd, 0, SEEK_SET); + if ((read_bytes = read(fd, buf, ID3V2_BUF_SIZE)) < header_size) + return false; + + buf [ID3V2_BUF_SIZE] = '\0'; + if ( parse_header( buf, read_bytes, &file ) ) + return false; + + copy_ay_fields( &file, id3, 0 ); + return true; +} + +bool get_ay_metadata(int fd, struct mp3entry* id3) +{ + char ay_type[8]; + if ((lseek(fd, 0, SEEK_SET) < 0) || + read(fd, ay_type, 8) < 8) + return false; + + id3->vbr = false; + id3->filesize = filesize(fd); + + id3->bitrate = 706; + id3->frequency = 44100; + + /* Make sure this is a ZX Ay file */ + if (memcmp( ay_type, "ZXAYEMUL", 8 ) != 0) + return false; + + return parse_ay_header(fd, id3); +} diff --git a/apps/metadata/gbs.c b/apps/metadata/gbs.c new file mode 100644 index 0000000000..796db5932f --- /dev/null +++ b/apps/metadata/gbs.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" + +bool parse_gbs_header(int fd, struct mp3entry* id3) +{ + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->path; + lseek(fd, 0, SEEK_SET); + if (read(fd, buf, 112) < 112) + return false; + + /* Calculate track length with number of subtracks */ + id3->length = buf[4] * 1000; + + /* If meta info was found in the m3u skip next step */ + if (id3->title && id3->title[0]) return true; + + char *p = id3->id3v2buf; + + /* Some metadata entries have 32 bytes length */ + /* Game */ + memcpy(p, &buf[16], 32); *(p + 33) = '\0'; + id3->title = p; + p += strlen(p)+1; + + /* Artist */ + memcpy(p, &buf[48], 32); *(p + 33) = '\0'; + id3->artist = p; + p += strlen(p)+1; + + /* Copyright */ + memcpy(p, &buf[80], 32); *(p + 33) = '\0'; + id3->album = p; + + return true; +} + +bool get_gbs_metadata(int fd, struct mp3entry* id3) +{ + char gbs_type[3]; + if ((lseek(fd, 0, SEEK_SET) < 0) || + (read(fd, gbs_type, 3) < 3)) + return false; + + id3->vbr = false; + id3->filesize = filesize(fd); + /* we only render 16 bits, 44.1KHz, Stereo */ + id3->bitrate = 706; + id3->frequency = 44100; + + /* Check for GBS magic */ + if (memcmp( gbs_type, "GBS", 3 ) != 0) + return false; + + return parse_gbs_header(fd, id3); +} diff --git a/apps/metadata/hes.c b/apps/metadata/hes.c new file mode 100644 index 0000000000..6d99d523cb --- /dev/null +++ b/apps/metadata/hes.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" +#include "plugin.h" + +bool get_hes_metadata(int fd, struct mp3entry* id3) +{ + /* Use the id3v2 buffer part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->id3v2buf; + int read_bytes; + + if ((lseek(fd, 0, SEEK_SET) < 0) + || ((read_bytes = read(fd, buf, 4)) < 4)) + return false; + + /* Verify this is a HES file */ + if (memcmp(buf,"HESM",4) != 0) + return false; + + id3->vbr = false; + id3->filesize = filesize(fd); + /* we only render 16 bits, 44.1KHz, Stereo */ + id3->bitrate = 706; + id3->frequency = 44100; + + /* Set default track count (length)*/ + id3->length = 255 * 1000; + + return true; +} + diff --git a/apps/metadata/kss.c b/apps/metadata/kss.c new file mode 100644 index 0000000000..ecb59b8353 --- /dev/null +++ b/apps/metadata/kss.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" + +bool parse_kss_header(int fd, struct mp3entry* id3) +{ + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->path; + + lseek(fd, 0, SEEK_SET); + if (read(fd, buf, 0x20) < 0x20) + return false; + + /* calculate track length with number of tracks */ + id3->length = 0; + if (buf[14] == 0x10) { + id3->length = (get_short_le((void *)(buf + 26)) + 1) * 1000; + } + + if (id3->length <= 0) + id3->length = 255 * 1000; /* 255 tracks */ + + return true; +} + + +bool get_kss_metadata(int fd, struct mp3entry* id3) +{ + uint32_t kss_type; + if ((lseek(fd, 0, SEEK_SET) < 0) || + read_uint32be(fd, &kss_type) != (int)sizeof(kss_type)) + return false; + + id3->vbr = false; + id3->filesize = filesize(fd); + /* we only render 16 bits, 44.1KHz, Stereo */ + id3->bitrate = 706; + id3->frequency = 44100; + + /* Make sure this is an SGC file */ + if (kss_type != FOURCC('K','S','C','C') && kss_type != FOURCC('K','S','S','X')) + return false; + + return parse_kss_header(fd, id3); +} diff --git a/apps/metadata/metadata_common.c b/apps/metadata/metadata_common.c index 6c420d921f..1ad89d1b7a 100644 --- a/apps/metadata/metadata_common.c +++ b/apps/metadata/metadata_common.c @@ -178,6 +178,14 @@ unsigned long get_long_be(void* buf) return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; } +/* Read an unaligned 16-bit little endian short from buffer. */ +unsigned short get_short_be(void* buf) +{ + unsigned char* p = (unsigned char*) buf; + + return (p[0] << 8) | p[1]; +} + /* Read an unaligned 32-bit little endian long from buffer. */ long get_slong(void* buf) { diff --git a/apps/metadata/metadata_common.h b/apps/metadata/metadata_common.h index b2c76afb45..a48c2a4e89 100644 --- a/apps/metadata/metadata_common.h +++ b/apps/metadata/metadata_common.h @@ -62,6 +62,7 @@ uint64_t get_uint64_le(void* buf); unsigned long get_long_le(void* buf); unsigned short get_short_le(void* buf); unsigned long get_long_be(void* buf); +unsigned short get_short_be(void* buf); long get_slong(void* buf); unsigned long get_itunes_int32(char* value, int count); long parse_tag(const char* name, char* value, struct mp3entry* id3, diff --git a/apps/metadata/metadata_parsers.h b/apps/metadata/metadata_parsers.h index 7797b47094..adb7a82cd5 100644 --- a/apps/metadata/metadata_parsers.h +++ b/apps/metadata/metadata_parsers.h @@ -47,3 +47,9 @@ bool get_au_metadata(int fd, struct mp3entry* id3); bool get_vox_metadata(int fd, struct mp3entry* id3); bool get_wave64_metadata(int fd, struct mp3entry* id3); bool get_tta_metadata(int fd, struct mp3entry* id3); +bool get_ay_metadata(int fd, struct mp3entry* id3); +bool get_gbs_metadata(int fd, struct mp3entry* id3); +bool get_hes_metadata(int fd, struct mp3entry* id3); +bool get_sgc_metadata(int fd, struct mp3entry* id3); +bool get_vgm_metadata(int fd, struct mp3entry* id3); +bool get_kss_metadata(int fd, struct mp3entry* id3); diff --git a/apps/metadata/nsf.c b/apps/metadata/nsf.c index 29fd8475bb..2fa6f36b12 100644 --- a/apps/metadata/nsf.c +++ b/apps/metadata/nsf.c @@ -11,6 +11,9 @@ #include "rbunicode.h" #include "string-extra.h" +/* NOTE: This file was modified to work properly with the new nsf codec based + on Game_Music_Emu */ + struct NESM_HEADER { uint32_t nHeader; @@ -66,7 +69,7 @@ static bool parse_nsfe(int fd, struct mp3entry *id3) /* default values */ info.nTrackCount = 1; - id3->length = 2*1000*60; + id3->length = 150 * 1000; /* begin reading chunks */ while (!(chunks_found & CHUNK_NEND)) @@ -210,6 +213,10 @@ static bool parse_nsfe(int fd, struct mp3entry *id3) if (track_count | playlist_count) id3->length = MAX(track_count, playlist_count)*1000; + /* Single subtrack files will be treated differently + by gme's nsf codec */ + if (id3->length <= 1000) id3->length = 150 * 1000; + /* * if we exited the while loop without a 'return', we must have hit an NEND * chunk if this is the case, the file was layed out as it was expected. @@ -230,7 +237,7 @@ static bool parse_nesm(int fd, struct mp3entry *id3) return false; /* Length */ - id3->length = hdr.nTrackCount*1000; + id3->length = (hdr.nTrackCount > 1 ? hdr.nTrackCount : 150) * 1000; /* Title */ id3->title = p; @@ -250,7 +257,6 @@ static bool parse_nesm(int fd, struct mp3entry *id3) bool get_nsf_metadata(int fd, struct mp3entry* id3) { uint32_t nsf_type; - if (lseek(fd, 0, SEEK_SET) < 0 || read_uint32be(fd, &nsf_type) != (int)sizeof(nsf_type)) return false; diff --git a/apps/metadata/sgc.c b/apps/metadata/sgc.c new file mode 100644 index 0000000000..9e16de2c76 --- /dev/null +++ b/apps/metadata/sgc.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" + +bool parse_sgc_header(int fd, struct mp3entry* id3) +{ + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->path; + + lseek(fd, 0, SEEK_SET); + if (read(fd, buf, 0xA0) < 0xA0) + return false; + + /* calculate track length with number of tracks */ + id3->length = buf[37] * 1000; + + /* If meta info was found in the m3u skip next step */ + if (id3->title && id3->title[0]) return true; + + char *p = id3->id3v2buf; + + /* Some metadata entries have 32 bytes length */ + /* Game */ + memcpy(p, &buf[64], 32); *(p + 33) = '\0'; + id3->title = p; + p += strlen(p)+1; + + /* Artist */ + memcpy(p, &buf[96], 32); *(p + 33) = '\0'; + id3->artist = p; + p += strlen(p)+1; + + /* Copyright */ + memcpy(p, &buf[128], 32); *(p + 33) = '\0'; + id3->album = p; + p += strlen(p)+1; + return true; +} + + +bool get_sgc_metadata(int fd, struct mp3entry* id3) +{ + uint32_t sgc_type; + if ((lseek(fd, 0, SEEK_SET) < 0) || + read_uint32be(fd, &sgc_type) != (int)sizeof(sgc_type)) + return false; + + id3->vbr = false; + id3->filesize = filesize(fd); + /* we only render 16 bits, 44.1KHz, Stereo */ + id3->bitrate = 706; + id3->frequency = 44100; + + /* Make sure this is an SGC file */ + if (sgc_type != FOURCC('S','G','C',0x1A)) + return false; + + return parse_sgc_header(fd, id3); +} diff --git a/apps/metadata/vgm.c b/apps/metadata/vgm.c new file mode 100644 index 0000000000..9ea95b3939 --- /dev/null +++ b/apps/metadata/vgm.c @@ -0,0 +1,195 @@ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" + +/* Ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */ + +typedef unsigned char byte; + +enum { header_size = 0x40 }; +enum { max_field = 64 }; + +struct header_t +{ + char tag [4]; + byte data_size [4]; + byte version [4]; + byte psg_rate [4]; + byte ym2413_rate [4]; + byte gd3_offset [4]; + byte track_duration [4]; + byte loop_offset [4]; + byte loop_duration [4]; + byte frame_rate [4]; + byte noise_feedback [2]; + byte noise_width; + byte unused1; + byte ym2612_rate [4]; + byte ym2151_rate [4]; + byte data_offset [4]; + byte unused2 [8]; +}; + +static byte const* skip_gd3_str( byte const* in, byte const* end ) +{ + while ( end - in >= 2 ) + { + in += 2; + if ( !(in [-2] | in [-1]) ) + break; + } + return in; +} + +static byte const* get_gd3_str( byte const* in, byte const* end, char* field ) +{ + byte const* mid = skip_gd3_str( in, end ); + int len = (mid - in) / 2 - 1; + if ( field && len > 0 ) + { + len = len < (int) max_field ? len : (int) max_field; + + field [len] = 0; + /* Conver to utf8 */ + utf16LEdecode( in, field, len ); + + /* Copy string back to id3v2buf */ + strcpy( (char*) in, field ); + } + return mid; +} + +static byte const* get_gd3_pair( byte const* in, byte const* end, char* field ) +{ + return skip_gd3_str( get_gd3_str( in, end, field ), end ); +} + +static void parse_gd3( byte const* in, byte const* end, struct mp3entry* id3 ) +{ + char* p = id3->path; + id3->title = (char *) in; + in = get_gd3_pair( in, end, p ); /* Song */ + + id3->album = (char *) in; + in = get_gd3_pair( in, end, p ); /* Game */ + + in = get_gd3_pair( in, end, NULL ); /* System */ + + id3->artist = (char *) in; + in = get_gd3_pair( in, end, p ); /* Author */ + +#if MEMORYSIZE > 2 + in = get_gd3_str ( in, end, NULL ); /* Copyright */ + in = get_gd3_pair( in, end, NULL ); /* Dumper */ + + id3->comment = (char *) in; + in = get_gd3_str ( in, end, p ); /* Comment */ +#endif +} + +int const gd3_header_size = 12; + +static long check_gd3_header( byte* h, long remain ) +{ + if ( remain < gd3_header_size ) return 0; + if ( memcmp( h, "Gd3 ", 4 ) ) return 0; + if ( get_long_le( h + 4 ) >= 0x200 ) return 0; + + long gd3_size = get_long_le( h + 8 ); + if ( gd3_size > remain - gd3_header_size ) + gd3_size = remain - gd3_header_size; + + return gd3_size; +} + +static void get_vgm_length( struct header_t* h, struct mp3entry* id3 ) +{ + long length = get_long_le( h->track_duration ) * 10 / 441; + if ( length > 0 ) + { + long loop_length = 0, intro_length = 0; + long loop = get_long_le( h->loop_duration ); + if ( loop > 0 && get_long_le( h->loop_offset ) ) + { + loop_length = loop * 10 / 441; + intro_length = length - loop_length; + } + else + { + intro_length = length; /* make it clear that track is no longer than length */ + loop_length = 0; + } + + id3->length = intro_length + 2 * loop_length; /* intro + 2 loops */ + return; + } + + id3->length = 150 * 1000; /* 2.5 minutes */ +} + +bool get_vgm_metadata(int fd, struct mp3entry* id3) +{ + /* Use the id3v2 part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->id3v2buf; + int read_bytes; + + memset(buf, 0, ID3V2_BUF_SIZE); + if ((lseek(fd, 0, SEEK_SET) < 0) + || ((read_bytes = read(fd, buf, header_size)) < header_size)) + { + return false; + } + + id3->vbr = false; + id3->filesize = filesize(fd); + + id3->bitrate = 706; + id3->frequency = 44100; + + /* If file is gzipped, will get metadata later */ + if (memcmp(buf, "Vgm ", 4)) + { + /* We must set a default song length here because + the codec can't do it anymore */ + id3->length = 150 * 1000; /* 2.5 minutes */ + return true; + } + + /* Get song length from header */ + struct header_t* header = (struct header_t*) buf; + get_vgm_length( header, id3 ); + + long gd3_offset = get_long_le( header->gd3_offset ) - 0x2C; + + /* No gd3 tag found */ + if ( gd3_offset < 0 ) + return true; + + /* Seek to gd3 offset and read as + many bytes posible */ + gd3_offset = id3->filesize - (header_size + gd3_offset); + if ((lseek(fd, -gd3_offset, SEEK_END) < 0) + || ((read_bytes = read(fd, buf, ID3V2_BUF_SIZE)) <= 0)) + return true; + + byte* gd3 = buf; + long gd3_size = check_gd3_header( gd3, read_bytes ); + + /* GD3 tag is zero */ + if ( gd3_size == 0 ) + return true; + + /* Finally, parse gd3 tag */ + if ( gd3 ) + parse_gd3( gd3 + gd3_header_size, gd3 + read_bytes, id3 ); + + return true; +} diff --git a/docs/CREDITS b/docs/CREDITS index 0156961fc9..d3825273ae 100644 --- a/docs/CREDITS +++ b/docs/CREDITS @@ -599,6 +599,7 @@ Sean Bartell Seheon Ryu Asier Arsuaga Vencislav Atanasov +Mauricio Garrido The libmad team The wavpack team diff --git a/manual/appendix/file_formats.tex b/manual/appendix/file_formats.tex index 4e5d96aedf..66fe397c03 100644 --- a/manual/appendix/file_formats.tex +++ b/manual/appendix/file_formats.tex @@ -209,6 +209,29 @@ Synthetic music Mobile Application Format & \fname{.mmf} & PCM/ADPCM only \\ + Game Boy Sound Format + & \fname{.gbs} + & Progress bar and seek use subtracks instead of seconds.\\ + AY Sound Chip Music + & \fname{.ay} + & Progress bar and seek use subtracks instead of seconds for + multitrack files.\\ + Hudson Entertainment System Sound Format + & \fname{.hes} + & Progress bar and seek use subtracks instead of seconds.\\ + MSX Konami Sound System + & \fname{.kss} + & Progress bar and seek use subtracks instead of seconds.\\ + SMS/GG/CV Sound Format + & \fname{.sgc} + & Supports Sega Master System and Game Gear Sound Format. + Progress bar and seek use subtracks instead of seconds.\\ + Video Game Music Format + & \fname{.vgm} + & \\ + Gzipped Video Game Music Format + & \fname{.vgz} + & \\ MOD & \fname{.mod} & \\ @@ -227,7 +250,7 @@ \end{rbtabular} \subsection{Codec featureset} - \begin{rbtabular}{.90\textwidth}{lXXX}% + \begin{rbtabular}{.95\textwidth}{lXXX}% {\textbf{Format} & \textbf{Seek} & \textbf{Resume} & \textbf{Gapless}}{}{} ATSC A/52 (AC3) & x & x & \\ ADX & x & & \\ @@ -252,6 +275,13 @@ Wavpack & x & x & x \\ Atari Sound Format & x & & \\ Synthetic music Mobile Application Format & x & x & \\ + Game Boy Sound Format & x & & \\ + AY Sound Chip Music & x & & \\ + Hudson Entertainment System Sound Format & x & & \\ + MSX Konami Sound System & x & & \\ + SMS/GG/CV Sound Format & x & & \\ + Video Game Music Format & x & x & \\ + Gzipped Video Game Music Format & x & x & \\ MOD & x & & \\ NES Sound Format & x & & \\ Atari SAP & x & & \\ @@ -259,8 +289,10 @@ SPC700 & x & & \\ \end{rbtabular} - \note{The seek implementations of NES Sound Format and Sound Interface Device - use subtracks instead of seconds, whereas each subtrack equals a second.} + \note{The seek implementations of NES Sound Format, Sound Interface Device, + Game Boy Sound Format, AY Sound Chip Music, Hudson Entertainment System Sound, + Format, MSX Konami Sound System and SMS/GG/CV Sound Format use subtracks + instead of seconds, whereas each subtrack equals a second.} \section{\label{ref:SupportedMetadata}Supported metadata tags} Rockbox supports different metadata formats. In general those tag formats @@ -281,13 +313,14 @@ MP4 & \fname{.m4a}, \fname{.m4b}, \fname{.mp4} \\ ASF & \fname{.wma}, \fname{.wmv}, \fname{.asf} \\ Codec specific & \fname{.mmf}, \fname{.mod}, \fname{.nsf}, \fname{.nsfe}, - \fname{.sap}, \fname{.sid}, \fname{.spc} \\ + \fname{.sap}, \fname{.sid}, \fname{.spc}, \fname{.gbs}, + \fname{.ay}, \fname{.kss}, \fname{.sgc}, \fname{.vgm} \\ None & \fname{.a52}, \fname{.ac3}, \fname{.adx}, \fname{.oma}, \fname{.aa3}, \fname{.aif}, \fname{.aiff}, \fname{.au}, \fname{.snd}, \fname{.shn}, \fname{.vox}, \fname{.w64}, \fname{.wav}, \fname{.cmc}, \fname{.cm3}, \fname{.cmr}, \fname{.cms}, \fname{.dmc}, \fname{.dlt}, \fname{.mpt}, - \fname{.mpd} \\ + \fname{.mpd}, \fname{.hes}, \fname{.vgz} \\ \end{rbtabular} \subsection{Featureset for generic metadata tags} @@ -322,17 +355,21 @@ Replaygain & \fname{.mpc}\\ Title & \fname{.tta}, \fname{.spc}, \fname{.mmf}, \fname{.sid}, \fname{.rm}, \fname{.ra}, \fname{.rmvb}, \fname{.nsf}, - \fname{.nsfe}, \fname{.mod}, \fname{.sap} \\ + \fname{.nsfe}, \fname{.mod}, \fname{.sap}, \fname{.gbs}, + \fname{.ay}, \fname{.sgc}, \fname{.vgm} \\ Artist & \fname{.tta}, \fname{.spc}, \fname{.mmf}, \fname{.sid}, \fname{.rm}, \fname{.ra}, \fname{.rmvb}, \fname{.nsf}, - \fname{.nsfe}, \fname{.sap} \\ - Album & \fname{.spc}, \fname{.sid}, \fname{.nsf}, \fname{.nsfe} \\ + \fname{.nsfe}, \fname{.sap}, \fname{.gbs}, \fname{.ay}, + \fname{.sgc}, \fname{.vgm} \\ + Album & \fname{.spc}, \fname{.sid}, \fname{.nsf}, \fname{.nsfe}, + \fname{.gbs}, \fname{.ay}, \fname{.sgc}, \fname{.vgm} \\ Genre & \fname{.tta}, \fname{.spc}, \fname{.sap} \\ Disc & \fname{.tta} \\ Track & \fname{.tta} \\ Year & \fname{.spc}, \fname{.sid}, \fname{.sap} \\ Composer & \fname{.mmf} \\ - Comment & \fname{.spc}, \fname{.rm}, \fname{.ra}, \fname{.rmvb} \\ + Comment & \fname{.spc}, \fname{.rm}, \fname{.ra}, \fname{.rmvb}, + \fname{.vgm} \\ Albumartist & None \\ Grouping & None \\ \end{rbtabular} -- cgit v1.2.3