/* * Snes9x - Portable Super Nintendo Entertainment System (TM) emulator. * * (c) Copyright 1996 - 2001 Gary Henderson (gary.henderson@ntlworld.com) and * Jerremy Koot (jkoot@snes9x.com) * * Super FX C emulator code * (c) Copyright 1997 - 1999 Ivar (ivar@snes9x.com) and * Gary Henderson. * * (c) Copyright 2014 - 2016 Daniel De Matteis. (UNDER NO CIRCUMSTANCE * WILL COMMERCIAL RIGHTS EVER BE APPROPRIATED TO ANY PARTY) * * Super FX assembler emulator code (c) Copyright 1998 zsKnight and _Demo_. * * DSP1 emulator code (c) Copyright 1998 Ivar, _Demo_ and Gary Henderson. * C4 asm and some C emulation code (c) Copyright 2000 zsKnight and _Demo_. * C4 C code (c) Copyright 2001 Gary Henderson (gary.henderson@ntlworld.com). * * DOS port code contains the works of other authors. See headers in * individual files. * * Snes9x homepage: http://www.snes9x.com * * Permission to use, copy, modify and distribute Snes9x in both binary and * source form, for non-commercial purposes, is hereby granted without fee, * providing that this license information and copyright notice appear with * all copies and any derived work. * * This software is provided 'as-is', without any express or implied * warranty. In no event shall the authors be held liable for any damages * arising from the use of this software. * * Snes9x is freeware for PERSONAL USE only. Commercial users should * seek permission of the copyright holders first. Commercial use includes * charging money for Snes9x or software derived from Snes9x. * * The copyright holders request that bug fixes and improvements to the code * should be forwarded to them so everyone can benefit from the modifications * in future versions. * * Super NES and Super Nintendo Entertainment System are trademarks of * Nintendo Co., Limited and its subsidiary companies. */ #include "snes9x.h" #include "spc700.h" #include "apu.h" #include "apumem.h" #include "soundux.h" #include "cpuexec.h" #undef ABS #define ABS(a) ((a) < 0 ? -(a) : (a)) #define ENVX_SHIFT 24 unsigned long AttackRate [16] = { 4100, 2600, 1500, 1000, 640, 380, 260, 160, 96, 64, 40, 24, 16, 10, 6, 1 }; unsigned long DecayRate [8] = { 1200, 740, 440, 290, 180, 110, 74, 37 }; unsigned long SustainRate [32] = { /*~0*/0xFFFFFFFF, 38000, 28000, 24000, 19000, 14000, 12000, 9400, 7100, 5900, 4700, 3500, 2900, 2400, 1800, 1500, 1200, 880, 740, 590, 440, 370, 290, 220, 180, 150, 110, 92, 74, 55, 37, 18 }; unsigned long IncreaseRate [32] = { /*~0*/0xFFFFFFFF, 4100, 3100, 2600, 2000, 1500, 1300, 1000, 770, 640, 510, 380, 320, 260, 190, 160, 130, 96, 80, 64, 48, 40, 32, 24, 20, 16, 12, 10, 8, 6, 4, 2 }; unsigned long DecreaseRateExp [32] = { /*~0*/0xFFFFFFFF, 38000, 28000, 24000, 19000, 14000, 12000, 9400, 7100, 5900, 4700, 3500, 2900, 2400, 1800, 1500, 1200, 880, 740, 590, 440, 370, 290, 220, 180, 150, 110, 92, 74, 55, 37, 18 }; // precalculated env rates for S9xSetEnvRate unsigned long AttackERate [16][10]; unsigned long DecayERate [8][10]; unsigned long SustainERate [32][10]; unsigned long IncreaseERate [32][10]; unsigned long DecreaseERateExp[32][10]; unsigned long KeyOffERate[10]; static INLINE void S9xSetEnvelopeRate(int channel, unsigned long rate, int direction, int target, unsigned int mode) { S9xSetEnvRate(&SoundData.channels [channel], rate, direction, target, mode); } static INLINE void S9xSetSoundADSR(int channel, int attack_ind, int decay_ind, int sustain_ind, int sustain_level, int release_rate) { int attack_rate = AttackRate [attack_ind]; int decay_rate = DecayRate [decay_ind]; int sustain_rate = SustainRate [sustain_ind]; // Hack for ROMs that use a very short attack rate, key on a // channel, then switch to decay mode. e.g. Final Fantasy II. if (attack_rate == 1) attack_rate = 0; SoundData.channels[channel].env_ind_attack = attack_ind; SoundData.channels[channel].env_ind_decay = decay_ind; SoundData.channels[channel].env_ind_sustain = sustain_ind; SoundData.channels[channel].attack_rate = attack_rate; SoundData.channels[channel].decay_rate = decay_rate; SoundData.channels[channel].sustain_rate = sustain_rate; SoundData.channels[channel].release_rate = release_rate; SoundData.channels[channel].sustain_level = sustain_level + 1; switch (SoundData.channels[channel].state) { case SOUND_ATTACK: S9xSetEnvelopeRate(channel, attack_rate, 1, 127, 0); break; case SOUND_DECAY: S9xSetEnvelopeRate(channel, decay_rate, -1, (MAX_ENVELOPE_HEIGHT * (sustain_level + 1)) >> 3, 1 << 28); break; case SOUND_SUSTAIN: S9xSetEnvelopeRate(channel, sustain_rate, -1, 0, 2 << 28); break; } } static INLINE void S9xSetSoundVolume(int channel, short volume_left, short volume_right) { Channel* ch = &SoundData.channels[channel]; if (!so.stereo) volume_left = (ABS(volume_right) + ABS(volume_left)) / 2; ch->volume_left = volume_left; ch->volume_right = volume_right; ch-> left_vol_level = (ch->envx * volume_left) / 128; ch->right_vol_level = (ch->envx * volume_right) / 128; } static INLINE void S9xSetMasterVolume(short volume_left, short volume_right) { if (Settings.DisableMasterVolume) { SoundData.master_volume_left = 127; SoundData.master_volume_right = 127; SoundData.master_volume [0] = SoundData.master_volume [1] = 127; } else { if (!so.stereo) volume_left = (ABS(volume_right) + ABS(volume_left)) / 2; SoundData.master_volume_left = volume_left; SoundData.master_volume_right = volume_right; SoundData.master_volume [0] = volume_left; SoundData.master_volume [1] = volume_right; } } static INLINE void S9xSetEchoVolume(short volume_left, short volume_right) { if (!so.stereo) volume_left = (ABS(volume_right) + ABS(volume_left)) / 2; SoundData.echo_volume_left = volume_left; SoundData.echo_volume_right = volume_right; SoundData.echo_volume [0] = volume_left; SoundData.echo_volume [1] = volume_right; } static INLINE void S9xSetEchoWriteEnable(uint8 byte) { SoundData.echo_write_enabled = byte; S9xSetEchoDelay(APU.DSP [APU_EDL] & 15); } static INLINE void S9xSetFrequencyModulationEnable(uint8 byte) { SoundData.pitch_mod = byte & (0xFE);//~1; } static INLINE int S9xGetEnvelopeHeight(int channel) { if ((Settings.SoundEnvelopeHeightReading || SNESGameFixes.SoundEnvelopeHeightReading2) && SoundData.channels[channel].state != SOUND_SILENT && SoundData.channels[channel].state != SOUND_GAIN) return (SoundData.channels[channel].envx); //siren fix from XPP if (SNESGameFixes.SoundEnvelopeHeightReading2 && SoundData.channels[channel].state != SOUND_SILENT) return (SoundData.channels[channel].envx); return (0); } static INLINE void S9xSetSoundHertz(int channel, int hertz) { SoundData.channels[channel].hertz = hertz; S9xSetSoundFrequency(channel, hertz); } static INLINE void S9xSetSoundType(int channel, int type_of_sound) { SoundData.channels[channel].type = type_of_sound; } static INLINE bool8 S9xSetSoundMode(int channel, int mode) { Channel* ch = &SoundData.channels[channel]; switch (mode) { case MODE_RELEASE: if (ch->mode != MODE_NONE) { ch->mode = MODE_RELEASE; return (TRUE); } break; case MODE_DECREASE_LINEAR: case MODE_DECREASE_EXPONENTIAL: case MODE_GAIN: if (ch->mode != MODE_RELEASE) { ch->mode = mode; if (ch->state != SOUND_SILENT) ch->state = mode; return (TRUE); } break; case MODE_INCREASE_LINEAR: case MODE_INCREASE_BENT_LINE: if (ch->mode != MODE_RELEASE) { ch->mode = mode; if (ch->state != SOUND_SILENT) ch->state = mode; return (TRUE); } break; case MODE_ADSR: if (ch->mode == MODE_NONE || ch->mode == MODE_ADSR) { ch->mode = mode; return (TRUE); } } return (FALSE); } static INLINE void S9xPlaySample(int channel) { Channel* ch = &SoundData.channels[channel]; ch->state = SOUND_SILENT; ch->mode = MODE_NONE; ch->envx = 0; ch->envxx = 0; ch->g_index = 0; ch->gaussian[0] = ch->gaussian[1] = ch->gaussian[2] = ch->gaussian[3] = 0; S9xFixEnvelope(channel, APU.DSP [APU_GAIN + (channel << 4)], APU.DSP [APU_ADSR1 + (channel << 4)], APU.DSP [APU_ADSR2 + (channel << 4)]); ch->sample_number = APU.DSP [APU_SRCN + channel * 0x10]; if (APU.DSP [APU_NON] & (1 << channel)) ch->type = SOUND_NOISE; else ch->type = SOUND_SAMPLE; S9xSetSoundFrequency(channel, ch->hertz); ch->loop = FALSE; ch->needs_decode = TRUE; ch->last_block = FALSE; ch->previous [0] = ch->previous[1] = 0; ch->block_pointer = *S9xGetSampleAddress(ch->sample_number); ch->sample_pointer = 0; ch->env_error = 0; ch->next_sample = 0; ch->interpolate = 0; ch->last_valid_header = 0; switch (ch->mode) { case MODE_ADSR: if (ch->attack_rate == 0) { if (ch->decay_rate == 0 || ch->sustain_level == 8) { ch->state = SOUND_SUSTAIN; ch->envx = (MAX_ENVELOPE_HEIGHT * ch->sustain_level) >> 3; S9xSetEnvRate(ch, ch->sustain_rate, -1, 0, 2 << 28); } else { ch->state = SOUND_DECAY; ch->envx = MAX_ENVELOPE_HEIGHT; S9xSetEnvRate(ch, ch->decay_rate, -1, (MAX_ENVELOPE_HEIGHT * ch->sustain_level) >> 3, 1 << 28); } ch-> left_vol_level = (ch->envx * ch->volume_left) / 128; ch->right_vol_level = (ch->envx * ch->volume_right) / 128; } else { ch->state = SOUND_ATTACK; ch->envx = 0; ch->left_vol_level = 0; ch->right_vol_level = 0; S9xSetEnvRate(ch, ch->attack_rate, 1, MAX_ENVELOPE_HEIGHT, 0); } ch->envxx = ch->envx << ENVX_SHIFT; break; case MODE_GAIN: ch->state = SOUND_GAIN; break; case MODE_INCREASE_LINEAR: ch->state = SOUND_INCREASE_LINEAR; break; case MODE_INCREASE_BENT_LINE: ch->state = SOUND_INCREASE_BENT_LINE; break; case MODE_DECREASE_LINEAR: ch->state = SOUND_DECREASE_LINEAR; break; case MODE_DECREASE_EXPONENTIAL: ch->state = SOUND_DECREASE_EXPONENTIAL; break; default: break; } S9xFixEnvelope(channel, APU.DSP [APU_GAIN + (channel << 4)], APU.DSP [APU_ADSR1 + (channel << 4)], APU.DSP [APU_ADSR2 + (channel << 4)]); } uint32 Spc700JumpTab_15; bool8 S9xInitAPU() { // notaz memset(&IAPU, 0, sizeof(IAPU)); IAPU.ExtraRAM = APU.ExtraRAM; IAPU.asmJumpTab = &Spc700JumpTab_15; // Normal case: ONE_APU_CYCLE = 15 IAPU.RAM = (uint8*) malloc(0x10000); IAPU.ShadowRAM = NULL;//(uint8 *) malloc (0x10000); IAPU.CachedSamples = NULL;//(uint8 *) malloc (0x40000); if (!IAPU.RAM /*|| !IAPU.ShadowRAM || !IAPU.CachedSamples*/) { S9xDeinitAPU(); return (FALSE); } return (TRUE); } void S9xDeinitAPU() { if (IAPU.RAM) { free((char*) IAPU.RAM); IAPU.RAM = NULL; } if (IAPU.ShadowRAM) { free((char*) IAPU.ShadowRAM); IAPU.ShadowRAM = NULL; } if (IAPU.CachedSamples) { free((char*) IAPU.CachedSamples); IAPU.CachedSamples = NULL; } } void S9xResetAPU(void) { int i, j; // Settings.APUEnabled = Settings.NextAPUEnabled; memset(IAPU.RAM, 0, 0x100); memset(IAPU.RAM + 0x20, 0xFF, 0x20); memset(IAPU.RAM + 0x60, 0xFF, 0x20); memset(IAPU.RAM + 0xA0, 0xFF, 0x20); memset(IAPU.RAM + 0xE0, 0xFF, 0x20); for (i = 1; i < 256; i++) memcpy(IAPU.RAM + (i << 8), IAPU.RAM, 0x100); //memset (IAPU.CachedSamples, 0, 0x40000); memset(APU.OutPorts, 0, 4); IAPU.DirectPage = IAPU.RAM; memmove(&IAPU.RAM [0xffc0], APUROM, sizeof(APUROM)); memmove(APU.ExtraRAM, APUROM, sizeof(APUROM)); IAPU.PC = IAPU.RAM + IAPU.RAM [0xfffe] + (IAPU.RAM [0xffff] << 8); CPU.APU_Cycles = 0; IAPU.YA.W = 0; IAPU.X = 0; IAPU.S = 0xff; IAPU.P = 0; S9xAPUUnpackStatus(); CPU.APU_APUExecuting = Settings.APUEnabled; #ifdef SPC700_SHUTDOWN IAPU.WaitAddress1 = NULL; IAPU.WaitAddress2 = NULL; IAPU.WaitCounter = 0; #endif APU.ShowROM = TRUE; IAPU.RAM [0xf1] = 0x80; for (i = 0; i < 3; i++) { APU.TimerEnabled [i] = FALSE; APU.TimerValueWritten [i] = 0; APU.TimerTarget [i] = 0; APU.Timer [i] = 0; } for (j = 0; j < 0x80; j++) APU.DSP [j] = 0; IAPU.TwoCycles = IAPU.OneCycle * 2; for (i = 0; i < 256; i++) S9xAPUCycles [i] = S9xAPUCycleLengths [i] * IAPU.OneCycle; APU.DSP [APU_ENDX] = 0; APU.DSP [APU_KOFF] = 0; APU.DSP [APU_KON] = 0; APU.DSP [APU_FLG] = APU_MUTE | APU_ECHO_DISABLED; APU.KeyedChannels = 0; S9xResetSound(TRUE); S9xSetEchoEnable(0); } extern int framecpto; void S9xSetAPUDSP(uint8 byte) { uint8 reg = IAPU.RAM [0xf2]; static uint8 KeyOn; static uint8 KeyOnPrev; int i; /* char str[64]; if (byte!=0) { sprintf(str,"fr : %d\nwrite dsp %d\ncpu cycle=%d pc=%04X",framecpto,byte,CPU.Cycles,CPU.PC-CPU.PCBase); S9xMessage(0,0,str); gp32_pause(); }*/ //extern uint8 spc_dump_dsp[0x100]; //spc_dump_dsp[reg] = byte; switch (reg) { case APU_FLG: if (byte & APU_SOFT_RESET) { APU.DSP [reg] = APU_MUTE | APU_ECHO_DISABLED | (byte & 0x1f); APU.DSP [APU_ENDX] = 0; APU.DSP [APU_KOFF] = 0; APU.DSP [APU_KON] = 0; S9xSetEchoWriteEnable(FALSE); // Kill sound S9xResetSound(FALSE); } else { S9xSetEchoWriteEnable(!(byte & APU_ECHO_DISABLED)); if (byte & APU_MUTE) S9xSetSoundMute(TRUE); else S9xSetSoundMute(FALSE); SoundData.noise_hertz = NoiseFreq [byte & 0x1f]; for (i = 0; i < 8; i++) { if (SoundData.channels [i].type == SOUND_NOISE) S9xSetSoundFrequency(i, SoundData.noise_hertz); } } break; case APU_NON: if (byte != APU.DSP [APU_NON]) { int c; uint8 mask = 1; for (c = 0; c < 8; c++, mask <<= 1) { int type; if (byte & mask) type = SOUND_NOISE; else type = SOUND_SAMPLE; S9xSetSoundType(c, type); } } break; case APU_MVOL_LEFT: if (byte != APU.DSP [APU_MVOL_LEFT]) { S9xSetMasterVolume((signed char) byte, (signed char) APU.DSP [APU_MVOL_RIGHT]); } break; case APU_MVOL_RIGHT: if (byte != APU.DSP [APU_MVOL_RIGHT]) { S9xSetMasterVolume((signed char) APU.DSP [APU_MVOL_LEFT], (signed char) byte); } break; case APU_EVOL_LEFT: if (byte != APU.DSP [APU_EVOL_LEFT]) { S9xSetEchoVolume((signed char) byte, (signed char) APU.DSP [APU_EVOL_RIGHT]); } break; case APU_EVOL_RIGHT: if (byte != APU.DSP [APU_EVOL_RIGHT]) { S9xSetEchoVolume((signed char) APU.DSP [APU_EVOL_LEFT], (signed char) byte); } break; case APU_ENDX: byte = 0; break; case APU_KOFF: // if (byte) { int c; uint8 mask = 1; for (c = 0; c < 8; c++, mask <<= 1) { if ((byte & mask) != 0) { if (APU.KeyedChannels & mask) { { KeyOnPrev &= ~mask; APU.KeyedChannels &= ~mask; APU.DSP [APU_KON] &= ~mask; //APU.DSP [APU_KOFF] |= mask; S9xSetSoundKeyOff(c); } } } else if ((KeyOnPrev & mask) != 0) { KeyOnPrev &= ~mask; APU.KeyedChannels |= mask; //APU.DSP [APU_KON] |= mask; APU.DSP [APU_KOFF] &= ~mask; APU.DSP [APU_ENDX] &= ~mask; S9xPlaySample(c); } } } //KeyOnPrev=0; APU.DSP [APU_KOFF] = byte; return; case APU_KON: if (byte) { int c; uint8 mask = 1; for (c = 0; c < 8; c++, mask <<= 1) { if ((byte & mask) != 0) { // Pac-In-Time requires that channels can be key-on // regardeless of their current state. if ((APU.DSP [APU_KOFF] & mask) == 0) { KeyOnPrev &= ~mask; APU.KeyedChannels |= mask; //APU.DSP [APU_KON] |= mask; //APU.DSP [APU_KOFF] &= ~mask; APU.DSP [APU_ENDX] &= ~mask; S9xPlaySample(c); } else KeyOn |= mask; } } } //spc_is_dumping_temp = byte; return; case APU_VOL_LEFT + 0x00: case APU_VOL_LEFT + 0x10: case APU_VOL_LEFT + 0x20: case APU_VOL_LEFT + 0x30: case APU_VOL_LEFT + 0x40: case APU_VOL_LEFT + 0x50: case APU_VOL_LEFT + 0x60: case APU_VOL_LEFT + 0x70: // At Shin Megami Tensei suggestion 6/11/00 // if (byte != APU.DSP [reg]) { S9xSetSoundVolume(reg >> 4, (signed char) byte, (signed char) APU.DSP [reg + 1]); } break; case APU_VOL_RIGHT + 0x00: case APU_VOL_RIGHT + 0x10: case APU_VOL_RIGHT + 0x20: case APU_VOL_RIGHT + 0x30: case APU_VOL_RIGHT + 0x40: case APU_VOL_RIGHT + 0x50: case APU_VOL_RIGHT + 0x60: case APU_VOL_RIGHT + 0x70: // At Shin Megami Tensei suggestion 6/11/00 // if (byte != APU.DSP [reg]) { S9xSetSoundVolume(reg >> 4, (signed char) APU.DSP [reg - 1], (signed char) byte); } break; case APU_P_LOW + 0x00: case APU_P_LOW + 0x10: case APU_P_LOW + 0x20: case APU_P_LOW + 0x30: case APU_P_LOW + 0x40: case APU_P_LOW + 0x50: case APU_P_LOW + 0x60: case APU_P_LOW + 0x70: S9xSetSoundHertz(reg >> 4, ((byte + (APU.DSP [reg + 1] << 8)) & FREQUENCY_MASK) * 8); break; case APU_P_HIGH + 0x00: case APU_P_HIGH + 0x10: case APU_P_HIGH + 0x20: case APU_P_HIGH + 0x30: case APU_P_HIGH + 0x40: case APU_P_HIGH + 0x50: case APU_P_HIGH + 0x60: case APU_P_HIGH + 0x70: S9xSetSoundHertz(reg >> 4, (((byte << 8) + APU.DSP [reg - 1]) & FREQUENCY_MASK) * 8); break; case APU_SRCN + 0x00: case APU_SRCN + 0x10: case APU_SRCN + 0x20: case APU_SRCN + 0x30: case APU_SRCN + 0x40: case APU_SRCN + 0x50: case APU_SRCN + 0x60: case APU_SRCN + 0x70: if (byte != APU.DSP [reg]) { //S9xSetSoundSample (reg >> 4, byte); // notaz: seems to be unused? } break; case APU_ADSR1 + 0x00: case APU_ADSR1 + 0x10: case APU_ADSR1 + 0x20: case APU_ADSR1 + 0x30: case APU_ADSR1 + 0x40: case APU_ADSR1 + 0x50: case APU_ADSR1 + 0x60: case APU_ADSR1 + 0x70: if (byte != APU.DSP [reg]) { { S9xFixEnvelope(reg >> 4, APU.DSP [reg + 2], byte, APU.DSP [reg + 1]); } } break; case APU_ADSR2 + 0x00: case APU_ADSR2 + 0x10: case APU_ADSR2 + 0x20: case APU_ADSR2 + 0x30: case APU_ADSR2 + 0x40: case APU_ADSR2 + 0x50: case APU_ADSR2 + 0x60: case APU_ADSR2 + 0x70: if (byte != APU.DSP [reg]) { { S9xFixEnvelope(reg >> 4, APU.DSP [reg + 1], APU.DSP [reg - 1], byte); } } break; case APU_GAIN + 0x00: case APU_GAIN + 0x10: case APU_GAIN + 0x20: case APU_GAIN + 0x30: case APU_GAIN + 0x40: case APU_GAIN + 0x50: case APU_GAIN + 0x60: case APU_GAIN + 0x70: if (byte != APU.DSP [reg]) { { S9xFixEnvelope(reg >> 4, byte, APU.DSP [reg - 2], APU.DSP [reg - 1]); } } break; case APU_ENVX + 0x00: case APU_ENVX + 0x10: case APU_ENVX + 0x20: case APU_ENVX + 0x30: case APU_ENVX + 0x40: case APU_ENVX + 0x50: case APU_ENVX + 0x60: case APU_ENVX + 0x70: break; case APU_OUTX + 0x00: case APU_OUTX + 0x10: case APU_OUTX + 0x20: case APU_OUTX + 0x30: case APU_OUTX + 0x40: case APU_OUTX + 0x50: case APU_OUTX + 0x60: case APU_OUTX + 0x70: break; case APU_DIR: break; case APU_PMON: if (byte != APU.DSP [APU_PMON]) S9xSetFrequencyModulationEnable(byte); break; case APU_EON: if (byte != APU.DSP [APU_EON]) S9xSetEchoEnable(byte); break; case APU_EFB: S9xSetEchoFeedback((signed char) byte); break; case APU_ESA: break; case APU_EDL: S9xSetEchoDelay(byte & 0xf); break; case APU_C0: case APU_C1: case APU_C2: case APU_C3: case APU_C4: case APU_C5: case APU_C6: case APU_C7: S9xSetFilterCoefficient(reg >> 4, (signed char) byte); break; default: // XXX //printf ("Write %02x to unknown APU register %02x\n", byte, reg); break; } KeyOnPrev |= KeyOn; KeyOn = 0; if (reg < 0x80) APU.DSP [reg] = byte; } void S9xFixEnvelope(int channel, uint8 gain, uint8 adsr1, uint8 adsr2) { if (adsr1 & 0x80) { // ADSR mode // XXX: can DSP be switched to ADSR mode directly from GAIN/INCREASE/ // DECREASE mode? And if so, what stage of the sequence does it start // at? if (S9xSetSoundMode(channel, MODE_ADSR)) S9xSetSoundADSR(channel, adsr1 & 0xf, (adsr1 >> 4) & 7, adsr2 & 0x1f, (adsr2 >> 5) & 7, 8); } else { // Gain mode if ((gain & 0x80) == 0) { if (S9xSetSoundMode(channel, MODE_GAIN)) { S9xSetEnvelopeRate(channel, 0, 0, gain & 0x7f, 0); S9xSetEnvelopeHeight(channel, gain & 0x7f); } } else { if (gain & 0x40) { // Increase mode if (S9xSetSoundMode(channel, (gain & 0x20) ? MODE_INCREASE_BENT_LINE : MODE_INCREASE_LINEAR)) S9xSetEnvelopeRate(channel, IncreaseRate [gain & 0x1f], 1, 127, (3 << 28) | gain); } else { if (gain & 0x20) { if (S9xSetSoundMode(channel, MODE_DECREASE_EXPONENTIAL)) S9xSetEnvelopeRate(channel, DecreaseRateExp [gain & 0x1f] / 2, -1, 0, (4 << 28) | gain); } else { if (S9xSetSoundMode(channel, MODE_DECREASE_LINEAR)) S9xSetEnvelopeRate(channel, IncreaseRate [gain & 0x1f], -1, 0, (3 << 28) | gain); } } } } } void S9xSetAPUControl(uint8 byte) { //if (byte & 0x40) //printf ("*** Special SPC700 timing enabled\n"); if ((byte & 1) != 0 && !APU.TimerEnabled [0]) { APU.Timer [0] = 0; IAPU.RAM [0xfd] = 0; if ((APU.TimerTarget [0] = IAPU.RAM [0xfa]) == 0) APU.TimerTarget [0] = 0x100; } if ((byte & 2) != 0 && !APU.TimerEnabled [1]) { APU.Timer [1] = 0; IAPU.RAM [0xfe] = 0; if ((APU.TimerTarget [1] = IAPU.RAM [0xfb]) == 0) APU.TimerTarget [1] = 0x100; } if ((byte & 4) != 0 && !APU.TimerEnabled [2]) { APU.Timer [2] = 0; IAPU.RAM [0xff] = 0; if ((APU.TimerTarget [2] = IAPU.RAM [0xfc]) == 0) APU.TimerTarget [2] = 0x100; } APU.TimerEnabled [0] = byte & 1; APU.TimerEnabled [1] = (byte & 2) >> 1; APU.TimerEnabled [2] = (byte & 4) >> 2; if (byte & 0x10) IAPU.RAM [0xF4] = IAPU.RAM [0xF5] = 0; if (byte & 0x20) IAPU.RAM [0xF6] = IAPU.RAM [0xF7] = 0; if (byte & 0x80) { if (!APU.ShowROM) { memmove(&IAPU.RAM [0xffc0], APUROM, sizeof(APUROM)); APU.ShowROM = TRUE; } } else { if (APU.ShowROM) { APU.ShowROM = FALSE; memmove(&IAPU.RAM [0xffc0], APU.ExtraRAM, sizeof(APUROM)); } } IAPU.RAM [0xf1] = byte; } void S9xSetAPUTimer(uint16 Address, uint8 byte) { IAPU.RAM [Address] = byte; switch (Address) { case 0xfa: if ((APU.TimerTarget [0] = IAPU.RAM [0xfa]) == 0) APU.TimerTarget [0] = 0x100; APU.TimerValueWritten [0] = TRUE; break; case 0xfb: if ((APU.TimerTarget [1] = IAPU.RAM [0xfb]) == 0) APU.TimerTarget [1] = 0x100; APU.TimerValueWritten [1] = TRUE; break; case 0xfc: if ((APU.TimerTarget [2] = IAPU.RAM [0xfc]) == 0) APU.TimerTarget [2] = 0x100; APU.TimerValueWritten [2] = TRUE; break; } } uint8 S9xGetAPUDSP() { uint8 reg = IAPU.RAM [0xf2] & 0x7f; uint8 byte = APU.DSP [reg]; switch (reg) { case APU_KON: break; case APU_KOFF: break; case APU_OUTX + 0x00: case APU_OUTX + 0x10: case APU_OUTX + 0x20: case APU_OUTX + 0x30: case APU_OUTX + 0x40: case APU_OUTX + 0x50: case APU_OUTX + 0x60: case APU_OUTX + 0x70: if (SoundData.channels [reg >> 4].state == SOUND_SILENT) return (0); return ((SoundData.channels [reg >> 4].sample >> 8) | (SoundData.channels [reg >> 4].sample & 0xff)); case APU_ENVX + 0x00: case APU_ENVX + 0x10: case APU_ENVX + 0x20: case APU_ENVX + 0x30: case APU_ENVX + 0x40: case APU_ENVX + 0x50: case APU_ENVX + 0x60: case APU_ENVX + 0x70: return 0; // return ((uint8) S9xGetEnvelopeHeight (reg >> 4)); case APU_ENDX: // To fix speech in Magical Drop 2 6/11/00 // APU.DSP [APU_ENDX] = 0; break; default: break; } return (byte); }