/* src/scsp2.c: New, threadable SCSP implementation for Yabause Copyright 2004 Stephane Dallongeville Copyright 2004-2007 Theo Berkau Copyright 2006 Guillaume Duhamel Copyright 2010 Andrew Church This file is part of Yabause. Yabause 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. Yabause 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 General Public License for more details. You should have received a copy of the GNU General Public License along with Yabause; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "core.h" #include "debug.h" #include "error.h" #include "m68kcore.h" #include "memory.h" #include "scsp.h" #include "threads.h" #include "yabause.h" #include #include #undef round // In case math.h defines it #define round(x) ((int) (floor((x) + 0.5))) #undef ScspInit // Disable compatibility alias extern SoundInterface_struct *SNDCoreList[]; // Defined by each port /////////////////////////////////////////////////////////////////////////// // This SCSP implementation is designed to be runnable as an independent // thread, encompassing the SCSP emulation itself as well as the MC68EC000 // sound processor and the actual generation of PCM audio data. // // When running in multithreaded mode, the actual SCSP and M68K emulation // is performed via ScspThread(). This function is started as a subthread // (YAB_THREAD_SCSP), and loops over ScspDoExec() until scsp_thread_running // goes to zero, which is used as a signal for the thread to stop. // Synchronization is performed via the scsp_clock_target variable; the SCSP // thread sleeps until clock_target != clock, then calls ScspDoExec() for // (clock - clock_target) cycles. The main thread wakes up the subthread // both when clock_target is updated and when register writes, discussed // below, are submitted. // // Additionally, any register writes from outside the SCSP/M68K will be // processed synchronously in multithreaded mode, by passing the write // through a shared buffer (scsp_write_buffer_* variables). When the // thread loop detects scsp_write_buffer_size nonzero, it processes the // write before its next ScspDoExec() iteration and clears ..._size; the // main thread then waits for ..._size to return to zero before returning // from the write operation. (This will typically be no more expensive // than a pair of context switches, and it seems that SCSP register writes // from the SH-2 are uncommon.) // // The "PSP_*" macros scattered throughout the file are to support the // execution of the SCSP thread on the Media Engine CPU (ME) in the PSP. // The ME lacks cache coherence with the main CPU (SC), so special care // needs to be taken to avoid bugs arising from inconsistent cache states; // see the technical notes in README.PSP for details. These macros are all // no-ops on other platforms. //------------------------------------------------------------------------- // PSP cache management macros #ifdef PSP # include "psp/common.h" # include "psp/me.h" # include "psp/me-utility.h" # include "psp/misc.h" // psp_writeback_cache_for_scsp() declaration // Data section management (to avoid cache line collisions between CPUs) # define PSP_SECTION(name) \ __attribute__((section(".meshared.scsp." #name))) # define PSP_SECTION_START(name) \ __attribute__((section(".meshared.scsp." #name), aligned(64))) \ extern volatile char __scsp_sectstart_##name[64]; # define PSP_SECTION_END(name) \ __attribute__((section(".meshared.scsp." #name))) \ extern volatile char __scsp_sectend_##name; // Cache control # define PSP_WRITEBACK_CACHE(ptr,len) \ sceKernelDcacheWritebackRange((ptr), (len)) # define PSP_WRITEBACK_ALL() \ sceKernelDcacheWritebackAll() # define PSP_FLUSH_ALL() \ sceKernelDcacheWritebackInvalidateAll() // Uncached pointer access # define PSP_UCPTR(ptr) ((typeof(&*ptr))((uint32_t)(ptr) | 0x40000000)) // Uncached variable access (either read or write) # define PSP_UC(var) (*((typeof(&var))((uint32_t)(&var) | 0x40000000))) #else // !PSP # define PSP_SECTION(name) /*nothing*/ # define PSP_SECTION_START(name) /*nothing*/ # define PSP_SECTION_END(name) /*nothing*/ # define PSP_WRITEBACK_CACHE(ptr,len) /*nothing*/ # define PSP_WRITEBACK_ALL() /*nothing*/ # define PSP_FLUSH_ALL() /*nothing*/ # define PSP_UCPTR(ptr) ptr # define PSP_UC(var) var #endif //------------------------------------------------------------------------- // SCSP constants // SCSP hardware version (4 bits) #define SCSP_VERSION 0 // SCSP clock frequency (11.2896 MHz, or exactly 44100*256) #define SCSP_CLOCK_FREQ (44100 * 256) // SCSP output frequency #define SCSP_OUTPUT_FREQ (SCSP_CLOCK_FREQ / 256) // SCSP clock increment per 1/10 scanline #define SCSP_CLOCK_INC_NTSC (((u64)SCSP_CLOCK_FREQ<<20) * 1001 / 60000 / 263 / 10) #define SCSP_CLOCK_INC_PAL (((u64)SCSP_CLOCK_FREQ<<20) / 50 / 313 / 10) // Limit on execution time for a single thread loop (in SCSP clock cycles); // if the thread's delay exceeds this value, we stop in the main loop to // let the SCSP catch up #define SCSP_CLOCK_MAX_EXEC (SCSP_CLOCK_FREQ / 1000) // Sound RAM size #define SCSP_RAM_SIZE 0x80000 #define SCSP_RAM_MASK (SCSP_RAM_SIZE - 1) // Envelope phases #define SCSP_ENV_RELEASE 0 #define SCSP_ENV_SUSTAIN 1 #define SCSP_ENV_DECAY 2 #define SCSP_ENV_ATTACK 3 // LFO waveform types (equal to ALFOWS/PLFOWS values) #define SCSP_LFO_SAWTOOTH 0 #define SCSP_LFO_SQUARE 1 #define SCSP_LFO_TRIANGLE 2 #define SCSP_LFO_NOISE 3 // Bit sizes of fixed-point counters // Fractional part of frequency counter (determines accuracy of audio // playback frequency) #define SCSP_FREQ_LOW_BITS 10 // Integer part of envelope counter (determines resolution of attack/decay // envelope); also used to define envelope value range #define SCSP_ENV_HIGH_BITS 10 // Fractional part of envelope counter (determines accuracy of envelope timing) #define SCSP_ENV_LOW_BITS 10 // Integer part of LFO counter (determines resolution of LFO waveform); // also used to define LFO value range #define SCSP_LFO_HIGH_BITS 10 // Fractional part of LFO counter (determines accuracy of LFO frequency) #define SCSP_LFO_LOW_BITS 10 // Fractional part of TL attenuation lookup table (determines resolution of // per-voice volume control) #define SCSP_TL_BITS 10 // Envelope/waveform table data sizes and corresponding masks #define SCSP_ENV_LEN (1 << SCSP_ENV_HIGH_BITS) #define SCSP_ENV_MASK (SCSP_ENV_LEN - 1) #define SCSP_LFO_LEN (1 << SCSP_LFO_HIGH_BITS) #define SCSP_LFO_MASK (SCSP_LFO_LEN - 1) // Envelope attack/decay points (counter values) #define SCSP_ENV_ATTACK_START 0 #define SCSP_ENV_DECAY_START (SCSP_ENV_LEN << SCSP_ENV_LOW_BITS) #define SCSP_ENV_ATTACK_END (SCSP_ENV_DECAY_START - 1) #define SCSP_ENV_DECAY_END (((2 * SCSP_ENV_LEN) << SCSP_ENV_LOW_BITS) - 1) // Envelope attack/decay base times #define SCSP_ATTACK_TIME ((u32) (8 * SCSP_OUTPUT_FREQ)) #define SCSP_DECAY_TIME ((u32) (12 * SCSP_ATTACK_TIME)) // Interrupt bit numbers #define SCSP_INTERRUPT_MIDI_IN 3 // Data available in MIDI input buffer #define SCSP_INTERRUPT_DMA 4 // DMA complete #define SCSP_INTERRUPT_MANUAL 5 // 1 written to bit 5 of [MS]CIPD #define SCSP_INTERRUPT_TIMER_A 6 // Timer A reached 0xFF #define SCSP_INTERRUPT_TIMER_B 7 // Timer B reached 0xFF #define SCSP_INTERRUPT_TIMER_C 8 // Timer C reached 0xFF #define SCSP_INTERRUPT_MIDI_OUT 9 // MIDI output buffer became empty #define SCSP_INTERRUPT_SAMPLE 10 // Raised once per output sample // Interrupt target flags #define SCSP_INTTARGET_MAIN (1 << 0) // Interrupt to main CPU (SCU) #define SCSP_INTTARGET_SOUND (1 << 1) // Interrupt to sound CPU #define SCSP_INTTARGET_BOTH (SCSP_INTTARGET_MAIN | SCSP_INTTARGET_SOUND) // PCM output buffer size parameters #define SCSP_SOUND_LEN_NTSC (SCSP_OUTPUT_FREQ / 60) // Samples per frame #define SCSP_SOUND_LEN_PAL (SCSP_OUTPUT_FREQ / 50) // Reserve 10x the maximum samples per frame #define SCSP_SOUND_BUFSIZE (10 * SCSP_SOUND_LEN_PAL) // CDDA data buffer size in sectors (must be at least 3) #define CDDA_NUM_BUFFERS 3 // CDDA playback start delay in samples (see cdda_delay comments) #define CDDA_DELAY_SAMPLES 100 //------------------------------------------------------------------------- // Internal state data structures // Per-slot data structure typedef struct SlotState_struct { //////////// // Register fields // ISR $00 // [12] Write 1 to execute KEY state change u8 key; // [11] KEY state (on/off) u8 sbctl; // [10:9] Source bit control u8 ssctl; // [8:7] Sound source control u8 lpctl; // [6:5] Loop control u8 pcm8b; // [4] PCM sound format // [3:0] Start address (in bytes), high bits (19:16) // ISR $02 u32 sa; // [15:0] Start address (in bytes), low bits (15:0) // ISR $04 u16 lsa; // [15:0] Loop start address (in samples) // ISR $06 u16 lea; // [15:0] Loop end address (in samples) // ISR $08 u8 sr; // [15:11] Sustain rate u8 dr; // [10:6] Decay rate u8 eghold; // [5] Envelope hold (attack rate 0) flag u8 ar; // [4:0] Attack rate // ISR $0A u8 lpslnk; // [14] Loop start link (start decay on reaching LSA) u8 krs; // [13:10] Key rate scale u8 sl; // [9:5] Sustain level u8 rr; // [4:0] Release rate // ISR $0C u8 stwinh; // [9] Stack write inhibit flag u8 sdir; // [8] Sound direct output flag u8 tl; // [7:0] Total level // ISR $0E u8 mdl; // [15:12] Modulation level u8 mdx; // [11:6] Modulation source X u8 mdy; // [5:0] Modulation source Y // ISR $10 u8 oct; // [14:11] Octave (treated as signed -8..7) u16 fns; // [9:0] Frequency number switch // ISR $12 u8 lfore; // [15] LFO reset flag (1 = reset, 0 = count) u8 lfof; // [14:10] LFO frequency index u8 plfows; // [9:8] Pitch LFO waveform select u8 plfos; // [7:5] Pitch LFO sensitivity u8 alfows; // [4:3] Amplitude LFO waveform select u8 alfos; // [2:0] Amplitude LFO sensitivity // ISR $14 u8 isel; // [6:3] Input selector u8 imxl; // [2:0] Input mix level // ISR $16 u8 disdl; // [15:13] Direct data send level u8 dipan; // [12:8] Direct data pan position u8 efsdl; // [7:5] Effect data send level u8 efpan; // [2:0] Effect data pan position //////////// // Internal state // Audio generation routine (selected based on slot parameters) void (* FASTCALL audiogen)(struct SlotState_struct *slot, u32 len); const void *buf; // Pointer to sample data in sound RAM u32 addr_counter; // Address (playback) counter u32 addr_step; // Address counter increment u8 octave_shift; // Octave shift amount (0..15) u32 lsa_shifted; // lsa << SCSP_FREQ_LOW_BITS (for addr_counter) u32 lea_shifted; // lea << SCSP_FREQ_LOW_BITS (for addr_counter) u32 looplen_shifted;// (lea - lsa + 1) << SCSP_FREQ_LOW_BITS u32 env_phase; // Current envelope phase (attack/decay/...) s32 env_counter; // Envelope counter s32 env_step; // Envelope counter increment for current phase s32 env_target; // Envelope target value for advancing to next phase s32 env_step_a; // Envelope counter increment for attack phase s32 env_step_d; // Envelope counter increment for decay phase s32 env_step_s; // Envelope counter increment for sustain phase s32 env_step_r; // Envelope counter increment for release phase s32 last_env; // Last calculated envelope multiplier u8 krs_shift; // Shift count corresponding to KRS s32 sl_target; // Compare value corresponding to SL s32 tl_mult; // Envelope volume multiplier corresponding to TL u32 lfo_counter; // LFO counter (fixed point index into LFO waveforms) s32 lfo_step; // LFO counter increment, or -1 if in reset mode s32 *lfo_fm_wave; // LFO frequency modulation waveform pointer s32 *lfo_am_wave; // LFO amplitude modulation waveform pointer s8 lfo_fm_shift; // LFO frequency modulation strength, -1 if disabled s8 lfo_am_shift; // LFO amplitude modulation strength, -1 if disabled u8 outshift_l; // Output shift for left channel (down to 16 bits) u8 outshift_r; // Output shift for right channel (down to 16 bits) u8 imxl_shift; // Shift count for IMXL } SlotState; //------------------------------------ // Overall SCSP data structure typedef struct ScspState_struct { //////////// // Register fields // $400 u8 mem4mb; // [9] Sound RAM memory size flag (4Mbit vs. 2Mbit) u8 dac18b; // [8] DAC 18-bit output flag (ignored) u8 ver; // [7:4] Hardware version (fixed at 0) u8 mvol; // [3:0] Master volume // $402 u8 rbl; // [8:7] Ring buffer length (8192<Exec or M68KExecBP static s32 m68k_saved_cycles; // Requested minus actual cycles executed static M68KBreakpointInfo m68k_breakpoint[M68K_MAX_BREAKPOINTS]; static int m68k_num_breakpoints; static void (*M68KBreakpointCallback)(u32); static u8 m68k_in_breakpoint; //------------------------------------------------------------------------- // Local function declarations static void ScspThread(void); static void ScspDoExec(u32 cycles); static u32 ScspTimerCyclesLeft(u16 timer, u8 timer_scale); static void ScspUpdateTimer(u32 samples, u16 *timer_ptr, u8 timer_scale, int interrupt); static void ScspGenerateAudio(s32 *bufL, s32 *bufR, u32 samples); static void ScspGenerateAudioForSlot(SlotState *slot, u32 samples); static void ScspGenerateAudioForCDDA(s32 *bufL, s32 *bufR, u32 samples); static u8 FASTCALL ScspReadByteDirect(u32 address); static u16 FASTCALL ScspReadWordDirect(u32 address); static void FASTCALL ScspWriteByteDirect(u32 address, u8 data); static void FASTCALL ScspWriteWordDirect(u32 address, u16 data); static u16 ScspReadMonitor(void); static void ScspDoKeyOnOff(void); static void ScspKeyOn(SlotState *slot); static void ScspKeyOff(SlotState *slot); static void ScspUpdateSlotAddress(SlotState *slot); static void ScspUpdateSlotEnv(SlotState *slot); static void ScspUpdateSlotFunc(SlotState *slot); static u16 ScspMidiIn(void); static void ScspMidiOut(u8 data); static void ScspDoDMA(void); static void ScspSyncThread(void); static void ScspRaiseInterrupt(int which, int target); static void ScspCheckInterrupts(u16 mask, int target); static void ScspClearInterrupts(u16 mask, int target); static void ScspRunM68K(u32 cycles); static s32 FASTCALL M68KExecBP(s32 cycles); static int scsp_mute_flags = 0; static int scsp_volume = 100; /////////////////////////////////////////////////////////////////////////// // Single-slot audio generation routines and corresponding table. The // table is indexed by: // scsp_audiogen_func_table[F][A][S][L][R] // ^ ^ ^ ^ ^-- Right channel on/off (on = 1) // | | | `-- Left channel on/off (on = 1) // | | `-- Sample size 16/8 bit (16 bit = 1) // | `-- Amplitude modulation on/off (on = 1) // `-- Frequency modulation on/off (on = 1) /////////////////////////////////////////////////////////////////////////// // For convenience, we use a single, parameterized macro to define every // function, with 0 or 1 in each of the F/A/S/L/R parameters; the compiler // will optimize out the disabled branches. // A couple of handy sub-macros: #define ADDRESS (addr_counter >> SCSP_FREQ_LOW_BITS) #ifdef WORDS_BIGENDIAN #define ADDRESS_8BIT (ADDRESS) #else #define ADDRESS_8BIT (ADDRESS ^ 1) #endif #define ENV_POS (env_counter >> SCSP_ENV_LOW_BITS) #define LFO_POS ((slot->lfo_counter >> SCSP_LFO_LOW_BITS) & SCSP_LFO_MASK) #define DEFINE_AUDIOGEN(tag,F,A,S,L,R) \ static void FASTCALL audiogen_##tag(SlotState *slot, u32 len) \ { \ /* Load these first to avoid having to reload them every iteration */ \ u32 addr_counter = slot->addr_counter; \ const u32 addr_step = slot->addr_step; \ u32 env_counter = slot->env_counter; \ u32 env_step = slot->env_step; \ \ u32 pos; \ for (pos = 0; pos < len; pos++) \ { \ if (L || R) /* Don't bother with calculations if it's all silent */ \ { \ /* Compute envelope/TL multiplier for waveform data */ \ s32 env = scsp_env_table[ENV_POS] * slot->tl_mult >> SCSP_TL_BITS; \ if (A) \ env -= slot->lfo_am_wave[LFO_POS] >> slot->lfo_am_shift; \ slot->last_env = env; \ \ /* Apply envelope / channel volume to waveform data and output */ \ if (LIKELY(env > 0)) \ { \ s32 out; \ if (S) \ out = (s32) ((const s16 *)slot->buf)[ADDRESS]; \ else \ out = (s32) ((const s8 *)slot->buf)[ADDRESS_8BIT] << 8; \ out *= env; \ if (L) \ scsp_bufL[pos] += out >> slot->outshift_l; \ if (R) \ scsp_bufR[pos] += out >> slot->outshift_r; \ } \ } \ \ /* Update address counter, exiting if we reach the end of the data */ \ if (F) \ { \ /* FIXME: need to handle the case where LFO data range != 1<lfo_fm_wave[LFO_POS] \ << slot->lfo_fm_shift \ >> slot->octave_shift; \ } \ addr_counter += addr_step; \ if (UNLIKELY(addr_counter > slot->lea_shifted)) \ { \ /* FIXME: reverse/alternating loops not implemented */ \ if (slot->lpctl) \ { \ addr_counter = slot->lsa_shifted \ + ((addr_counter - slot->lsa_shifted) \ % slot->looplen_shifted); \ } \ else \ { \ env_counter = SCSP_ENV_DECAY_END; \ goto done; \ } \ } \ \ /* Update envelope counter, advancing the envelope phase as needed */ \ env_counter += env_step; \ if (UNLIKELY(env_counter >= slot->env_target)) \ { \ switch (slot->env_phase) \ { \ case SCSP_ENV_ATTACK: \ env_counter = SCSP_ENV_DECAY_START; \ env_step = slot->env_step = slot->env_step_d; \ slot->env_target = slot->sl_target; \ slot->env_phase = SCSP_ENV_DECAY; \ break; \ case SCSP_ENV_DECAY: \ env_counter = slot->sl_target; \ env_step = slot->env_step = slot->env_step_s; \ slot->env_target = SCSP_ENV_DECAY_END; \ slot->env_phase = SCSP_ENV_SUSTAIN; \ break; \ default: \ env_counter = SCSP_ENV_DECAY_END; \ env_step = slot->env_step = 0; \ slot->env_target = SCSP_ENV_DECAY_END + 1; \ goto done; \ } \ } \ \ /* Update the LFO counter if either LFO waveform is in use \ * (technically, we should update whenever slot->lfore == 0, but \ * we skip the update on non-modulated channels to save time) */ \ if (F || A) \ slot->lfo_counter += slot->lfo_step; \ } \ \ done: \ slot->addr_counter = addr_counter; \ slot->env_counter = env_counter; \ } //------------------------------------------------------------------------- // Define the actual audio generation functions. For simplicity, we name // each function using the state of its parameter flags, with uppercase for // an enabled flag and lowercase for a disabled flag. We also use the null // output function for all cases where L and R are zero, to avoid // unnecessary code bloat. DEFINE_AUDIOGEN(null, 0,0,0,0,0) DEFINE_AUDIOGEN(faslR, 0,0,0,0,1) DEFINE_AUDIOGEN(fasLr, 0,0,0,1,0) DEFINE_AUDIOGEN(fasLR, 0,0,0,1,1) DEFINE_AUDIOGEN(faSlR, 0,0,1,0,1) DEFINE_AUDIOGEN(faSLr, 0,0,1,1,0) DEFINE_AUDIOGEN(faSLR, 0,0,1,1,1) DEFINE_AUDIOGEN(fAslR, 0,1,0,0,1) DEFINE_AUDIOGEN(fAsLr, 0,1,0,1,0) DEFINE_AUDIOGEN(fAsLR, 0,1,0,1,1) DEFINE_AUDIOGEN(fASlR, 0,1,1,0,1) DEFINE_AUDIOGEN(fASLr, 0,1,1,1,0) DEFINE_AUDIOGEN(fASLR, 0,1,1,1,1) DEFINE_AUDIOGEN(FaslR, 1,0,0,0,1) DEFINE_AUDIOGEN(FasLr, 1,0,0,1,0) DEFINE_AUDIOGEN(FasLR, 1,0,0,1,1) DEFINE_AUDIOGEN(FaSlR, 1,0,1,0,1) DEFINE_AUDIOGEN(FaSLr, 1,0,1,1,0) DEFINE_AUDIOGEN(FaSLR, 1,0,1,1,1) DEFINE_AUDIOGEN(FAslR, 1,1,0,0,1) DEFINE_AUDIOGEN(FAsLr, 1,1,0,1,0) DEFINE_AUDIOGEN(FAsLR, 1,1,0,1,1) DEFINE_AUDIOGEN(FASlR, 1,1,1,0,1) DEFINE_AUDIOGEN(FASLr, 1,1,1,1,0) DEFINE_AUDIOGEN(FASLR, 1,1,1,1,1) // We don't need these anymore, so get rid of them #undef ADDRESS #undef ADDRESS_8BIT #undef ENV_POS #undef LFO_POS #undef DEFINE_AUDIOGEN //------------------------------------------------------------------------- // Define the function lookup table. static void (* FASTCALL scsp_audiogen_func_table[2][2][2][2][2])(SlotState *slot, u32 len) = { { // F==0 { // A==0 {{audiogen_null, audiogen_faslR}, {audiogen_fasLr, audiogen_fasLR}}, {{audiogen_null, audiogen_faSlR}, {audiogen_faSLr, audiogen_faSLR}} }, { // A==1 {{audiogen_null, audiogen_fAslR}, {audiogen_fAsLr, audiogen_fAsLR}}, {{audiogen_null, audiogen_fASlR}, {audiogen_fASLr, audiogen_fASLR}} } }, { // F==1 { // A==0 {{audiogen_null, audiogen_FaslR}, {audiogen_FasLr, audiogen_FasLR}}, {{audiogen_null, audiogen_FaSlR}, {audiogen_FaSLr, audiogen_FaSLR}} }, { // A==1 {{audiogen_null, audiogen_FAslR}, {audiogen_FAsLr, audiogen_FAsLR}}, {{audiogen_null, audiogen_FASlR}, {audiogen_FASLr, audiogen_FASLR}} } } }; /////////////////////////////////////////////////////////////////////////// // Initialization, configuration, and cleanup routines /////////////////////////////////////////////////////////////////////////// // ScspInit: Initialize the SCSP emulation. interrupt_handler should // specify a function to handle interrupts delivered to the SCU. // Must be called after M68KInit(); returns 0 on success, -1 on failure. int ScspInit(int coreid, void (*interrupt_handler)(void)) { int i, j; double x; if ((SoundRam = T2MemoryInit(0x80000)) == NULL) return -1; // Fill in lookup tables for (i = 0; i < SCSP_ENV_LEN; i++) { // Attack Curve (x^4 ?) x = pow(((double) (SCSP_ENV_MASK - i) / SCSP_ENV_LEN), 4); x *= (double) SCSP_ENV_LEN; scsp_env_table[i] = SCSP_ENV_MASK - (s32) floor(x); // Decay curve (x = linear) scsp_env_table[i + SCSP_ENV_LEN] = SCSP_ENV_MASK - i; } for (i = 0, j = 0; i < 32; i++) { double lfo_frequency, lfo_step; // Frequency divider follows the pattern 1,2,3,4, 6,8,10,12, 16,... j += 1 << (i >> 2); // Base LFO frequency is 44100/256 or ~172.3 Hz lfo_frequency = (44100.0 / 256.0) / j; // Calculate LFO address step per output sample; we use a literal // 44100 above but OUTPUT_FREQ here in case anyone wants to try // upsampling the audio output someday lfo_step = (lfo_frequency / SCSP_OUTPUT_FREQ) * SCSP_LFO_LEN; scsp_lfo_step[31 - i] = round(lfo_step * (1 << SCSP_LFO_LOW_BITS)); } for (i = 0; i < SCSP_LFO_LEN; i++) { // Amplitude modulation uses unsigned values which are subtracted // from the base envelope value scsp_lfo_wave_amp[SCSP_LFO_SAWTOOTH][i] = i; if (i < SCSP_LFO_LEN / 2) scsp_lfo_wave_amp[SCSP_LFO_SQUARE][i] = 0; else scsp_lfo_wave_amp[SCSP_LFO_SQUARE][i] = SCSP_LFO_MASK; if (i < SCSP_LFO_LEN / 2) scsp_lfo_wave_amp[SCSP_LFO_TRIANGLE][i] = i*2; else scsp_lfo_wave_amp[SCSP_LFO_TRIANGLE][i] = SCSP_LFO_MASK - ((i - SCSP_LFO_LEN/2) * 2); scsp_lfo_wave_amp[SCSP_LFO_NOISE][i] = rand() & SCSP_LFO_MASK; // FIXME: note that the noise generator output should be independent // of LFORE/LFOF // Frequency modulation uses signed values which are added to the // address counter if (i < SCSP_LFO_LEN / 2) scsp_lfo_wave_freq[SCSP_LFO_SAWTOOTH][i] = i; else scsp_lfo_wave_freq[SCSP_LFO_SAWTOOTH][i] = i - SCSP_LFO_LEN; if (i < SCSP_LFO_LEN / 2) scsp_lfo_wave_freq[SCSP_LFO_SQUARE][i] = SCSP_LFO_MASK - SCSP_LFO_LEN/2; else scsp_lfo_wave_freq[SCSP_LFO_SQUARE][i] = 0 - SCSP_LFO_LEN/2; if (i < SCSP_LFO_LEN / 4) scsp_lfo_wave_freq[SCSP_LFO_TRIANGLE][i] = i*2; else if (i < SCSP_LFO_LEN * 3 / 4) scsp_lfo_wave_freq[SCSP_LFO_TRIANGLE][i] = SCSP_LFO_MASK - i*2; else scsp_lfo_wave_freq[SCSP_LFO_TRIANGLE][i] = i*2 - SCSP_LFO_LEN*2; scsp_lfo_wave_freq[SCSP_LFO_NOISE][i] = scsp_lfo_wave_amp[SCSP_LFO_NOISE][i] - SCSP_LFO_LEN/2; } for (i = 0; i < 4; i++) { scsp_attack_rate[i] = 0; scsp_decay_rate[i] = 0; } for (i = 0; i < 60; i++) { x = 1.0 + ((i & 3) * 0.25); // Bits 0-1: x1.00, x1.25, x1.50, x1.75 x *= 1 << (i >> 2); // Bits 2-5: shift bits (x2^0 - x2^15) x *= SCSP_ENV_LEN << SCSP_ENV_LOW_BITS; // Adjust for envelope table size scsp_attack_rate[i + 4] = round(x / SCSP_ATTACK_TIME); if (scsp_attack_rate[i + 4] == 0) scsp_attack_rate[i + 4] = 1; scsp_decay_rate[i + 4] = round(x / SCSP_DECAY_TIME); if (scsp_decay_rate[i + 4] == 0) scsp_decay_rate[i + 4] = 1; } scsp_attack_rate[63] = SCSP_ENV_ATTACK_END; scsp_decay_rate[61] = scsp_decay_rate[60]; scsp_decay_rate[62] = scsp_decay_rate[60]; scsp_decay_rate[63] = scsp_decay_rate[60]; for (i = 64; i < 78; i++) { scsp_attack_rate[i] = scsp_attack_rate[63]; scsp_decay_rate[i] = scsp_decay_rate[63]; } for (i = 0; i < 256; i++) scsp_tl_table[i] = round(pow(2.0, -(i/16.0)) * (1 << SCSP_TL_BITS)); // Initialize the SCSP state scsp_interrupt_handler = interrupt_handler; scsp_clock_inc = yabsys.IsPal ? SCSP_CLOCK_INC_PAL : SCSP_CLOCK_INC_NTSC; ScspReset(); // Note that we NEVER reset the clock counter after initialization, // because in multithreaded mode, that would cause a race condition in // which the SCSP thread runs between the two assignments and detects // clock != clock_target, causing it to execute a huge number of cycles. // (We do, however, reset the accumulated fraction of a cycle inside // ScspReset().) scsp_clock = 0; scsp_clock_target = 0; // Initialize the M68K state if (M68K->Init() != 0) return -1; M68K->SetReadB(M68KReadByte); M68K->SetReadW(M68KReadWord); M68K->SetWriteB(M68KWriteByte); M68K->SetWriteW(M68KWriteWord); M68K->SetFetch(0x000000, 0x040000, (pointer)SoundRam); M68K->SetFetch(0x040000, 0x080000, (pointer)SoundRam); M68K->SetFetch(0x080000, 0x0C0000, (pointer)SoundRam); M68K->SetFetch(0x0C0000, 0x100000, (pointer)SoundRam); m68k_running = 0; m68k_execf = M68K->Exec; m68k_saved_cycles = 0; for (i = 0; i < MAX_BREAKPOINTS; i++) m68k_breakpoint[i].addr = 0xFFFFFFFF; m68k_num_breakpoints = 0; M68KBreakpointCallback = NULL; m68k_in_breakpoint = 0; // Set up sound output scsp_sound_genpos = 0; scsp_sound_left = 0; if (ScspChangeSoundCore(coreid) < 0) return -1; // Start a subthread if requested scsp_thread_running = 0; if (yabsys.UseThreads) { scsp_thread_running = 1; // Set now so the thread doesn't quit instantly PSP_FLUSH_ALL(); if (YabThreadStart(YAB_THREAD_SCSP, ScspThread) < 0) { SCSPLOG("Failed to start SCSP thread\n"); scsp_thread_running = 0; } } // Successfully initialized! return 0; } //------------------------------------------------------------------------- // ScspReset: Reset the SCSP to its power-on state, also stopping the M68K // processor. void ScspReset(void) { int slotnum; if (scsp_thread_running) ScspSyncThread(); scsp.mem4mb = 0; scsp.dac18b = 0; scsp.ver = 0; scsp.mvol = 0; scsp.rbl = 0; scsp.rbp = 0; scsp.mofull = 0; scsp.moemp = 1; scsp.miovf = 0; scsp.mifull = 0; scsp.miemp = 1; scsp.mibuf = 0; scsp.mobuf = 0; scsp.mslc = 0; scsp.ca = 0; scsp.dmea = 0; scsp.drga = 0; scsp.dgate = 0; scsp.ddir = 0; scsp.dexe = 0; scsp.dtlg = 0; scsp.tactl = 0; scsp.tima = 0xFF00; scsp.tbctl = 0; scsp.timb = 0xFF00; scsp.tcctl = 0; scsp.timc = 0xFF00; scsp.mcieb = 0; scsp.mcipd = 0; scsp.scilv0 = 0; scsp.scilv1 = 0; scsp.scilv2 = 0; scsp.scieb = 0; scsp.scipd = 0; memset(scsp_regcache, 0, sizeof(scsp_regcache)); scsp_regcache[0x400>>1] = SCSP_VERSION << 4; memset(scsp.stack, 0, sizeof(scsp.stack)); for (slotnum = 0; slotnum < 32; slotnum++) { memset(&scsp.slot[slotnum], 0, sizeof(scsp.slot[slotnum])); scsp.slot[slotnum].env_counter = SCSP_ENV_DECAY_END; // Slot off scsp.slot[slotnum].outshift_l = 31; // Output off scsp.slot[slotnum].outshift_r = 31; scsp.slot[slotnum].audiogen = audiogen_null; } scsp.sound_ram_mask = 0x3FFFF; scsp_clock_frac = 0; scsp.sample_timer = 0; scsp_main_interrupt_pending = 0; scsp_write_buffer_size = 0; cdda_next_in = 0; cdda_next_out = 0; cdda_delay = CDDA_DELAY_SAMPLES; m68k_running = 0; if (scsp_thread_running) PSP_FLUSH_ALL(); } //------------------------------------------------------------------------- // ScspChangeSoundCore: Change the module used for sound output. Returns // 0 on success, -1 on error. int ScspChangeSoundCore(int coreid) { int i; // Make sure the old core is freed if (SNDCore) SNDCore->DeInit(); // If the default was requested, use the first core in the list if (coreid == SNDCORE_DEFAULT) SNDCore = SNDCoreList[0]; else { // Otherwise, go through core list and find the id for (i = 0; SNDCoreList[i] != NULL; i++) { if (SNDCoreList[i]->id == coreid) { // Set to current core SNDCore = SNDCoreList[i]; break; } } } if (SNDCore == NULL) { SNDCore = &SNDDummy; return -1; } if (SNDCore->Init() == -1) { // Since it failed, instead of it being fatal, we'll just use the dummy // core instead // This might be helpful though. YabSetError(YAB_ERR_CANNOTINIT, (void *)SNDCore->Name); SNDCore = &SNDDummy; } if (SNDCore) { if (scsp_mute_flags) SNDCore->MuteAudio(); else SNDCore->UnMuteAudio(); SNDCore->SetVolume(scsp_volume); } return 0; } //------------------------------------------------------------------------- // ScspChangeVideoFormat: Update SCSP parameters for a change in video // format. type is nonzero for PAL (50Hz), zero for NTSC (59.94Hz) video. // Always returns 0 for success. int ScspChangeVideoFormat(int type) { scsp_clock_inc = yabsys.IsPal ? SCSP_CLOCK_INC_PAL : SCSP_CLOCK_INC_NTSC; SNDCore->ChangeVideoFormat(type ? 50 : 60); return 0; } //------------------------------------------------------------------------- // ScspSetFrameAccurate: Set whether sound should be generated with // frame-accurate timing. void ScspSetFrameAccurate(int on) { scsp_frame_accurate = (on != 0); } //------------------------------------------------------------------------- // ScspMuteAudio, ScspUnMuteAudio: Mute or unmute the sound output. Does // not affect actual SCSP processing. void ScspMuteAudio(int flags) { scsp_mute_flags |= flags; if (SNDCore && scsp_mute_flags) SNDCore->MuteAudio(); } void ScspUnMuteAudio(int flags) { scsp_mute_flags &= ~flags; if (SNDCore && (scsp_mute_flags == 0)) SNDCore->UnMuteAudio(); } //------------------------------------------------------------------------- // ScspSetVolume: Set the sound output volume. Does not affect actual // SCSP processing. void ScspSetVolume(int volume) { scsp_volume = volume; if (SNDCore) SNDCore->SetVolume(volume); } //------------------------------------------------------------------------- // ScspDeInit: Free all resources used by the SCSP emulation. void ScspDeInit(void) { if (scsp_thread_running) { scsp_thread_running = 0; // Tell the subthread to stop YabThreadWake(YAB_THREAD_SCSP); YabThreadWait(YAB_THREAD_SCSP); } if (SNDCore) SNDCore->DeInit(); SNDCore = NULL; if (SoundRam) T2MemoryDeInit(SoundRam); SoundRam = NULL; } /////////////////////////////////////////////////////////////////////////// // Main SCSP processing routine and internal helpers /////////////////////////////////////////////////////////////////////////// // ScspExec: Main SCSP processing routine. Executes (decilines/10.0) // scanlines worth of SCSP emulation; in multithreaded mode, bumps the // clock target by the same amount of time. void ScspExec(int decilines) { u32 new_target; scsp_clock_frac += scsp_clock_inc * decilines; new_target = scsp_clock_target + (scsp_clock_frac >> 20); scsp_clock_target = new_target; scsp_clock_frac &= 0xFFFFF; if (scsp_thread_running) { #ifdef PSP if (!psp_writeback_cache_for_scsp()) PSP_UC(scsp_clock_target) = new_target; // Push just this one through #endif while (new_target - PSP_UC(scsp_clock) > SCSP_CLOCK_MAX_EXEC) { YabThreadWake(YAB_THREAD_SCSP); YabThreadYield(); } if (PSP_UC(scsp_main_interrupt_pending)) { (*scsp_interrupt_handler)(); PSP_UC(scsp_main_interrupt_pending) = 0; } } else ScspDoExec(new_target - scsp_clock); } /////////////////////////////////////////////////////////////////////////// // ScspThread: Control routine for SCSP thread. Loops over ScspDoExec() // and SCSP write buffer processing until told to stop. static void ScspThread(void) { while (PSP_UC(scsp_thread_running)) { const u8 write_size = PSP_UC(scsp_write_buffer_size); u32 clock_cycles; if (write_size != 0) { const u32 address = PSP_UC(scsp_write_buffer_address); const u32 data = PSP_UC(scsp_write_buffer_data); if (write_size == 1) ScspWriteByteDirect(address, data); else if (write_size == 2) ScspWriteWordDirect(address, data); else { ScspWriteWordDirect(address, data >> 16); ScspWriteWordDirect(address+2, data & 0xFFFF); } PSP_UC(scsp_write_buffer_size) = 0; } clock_cycles = PSP_UC(scsp_clock_target) - scsp_clock; if (clock_cycles > SCSP_CLOCK_MAX_EXEC) clock_cycles = SCSP_CLOCK_MAX_EXEC; if (clock_cycles > 0) { ScspDoExec(clock_cycles); YabThreadYield(); } else YabThreadSleep(); } } /////////////////////////////////////////////////////////////////////////// // ScspDoExec: Main SCSP processing routine implementation. Runs M68K // code, updates timers, and generates samples for the given number of // SCSP clock cycles. static void ScspDoExec(u32 cycles) { #ifdef WIN32 s16 stereodata16[(44100 / 60) * 16]; //11760 #endif u32 cycles_left; u32 sample_count; u32 audio_free; // If any of the timer interrupts are enabled, give the M68K a chance // to respond to them immediately, so that music doesn't slow down if // the SCSP thread gets behind and executes a lot of cycles at once. sample_count = 0; cycles_left = cycles; while (cycles_left > 0) { u32 this_samples = 0; u32 this_cycles = cycles_left; if (scsp.scieb & (1 << SCSP_INTERRUPT_TIMER_A)) this_cycles = MIN(this_cycles, ScspTimerCyclesLeft(scsp.tima, scsp.tactl)); if (scsp.scieb & (1 << SCSP_INTERRUPT_TIMER_B)) this_cycles = MIN(this_cycles, ScspTimerCyclesLeft(scsp.timb, scsp.tbctl)); if (scsp.scieb & (1 << SCSP_INTERRUPT_TIMER_C)) this_cycles = MIN(this_cycles, ScspTimerCyclesLeft(scsp.timc, scsp.tcctl)); scsp.sample_timer += this_cycles; this_samples = scsp.sample_timer >> 8; scsp.sample_timer &= 0xFF; cycles_left -= this_cycles; sample_count += this_samples; ScspRunM68K(this_cycles); ScspUpdateTimer(this_samples, &scsp.tima, scsp.tactl, SCSP_INTERRUPT_TIMER_A); ScspUpdateTimer(this_samples, &scsp.timb, scsp.tbctl, SCSP_INTERRUPT_TIMER_B); ScspUpdateTimer(this_samples, &scsp.timc, scsp.tcctl, SCSP_INTERRUPT_TIMER_C); } if (scsp_frame_accurate) { s32 *bufL, *bufR; // Update sound buffers if (scsp_sound_left + sample_count > SCSP_SOUND_BUFSIZE) { u32 overrun = (scsp_sound_left + sample_count) - SCSP_SOUND_BUFSIZE; SCSPLOG("WARNING: Sound buffer overrun, %u samples\n", (int)overrun); scsp_sound_left -= overrun; } while (sample_count > 0) { u32 this_count = sample_count; if (scsp_sound_genpos >= SCSP_SOUND_BUFSIZE) scsp_sound_genpos = 0; if (this_count > SCSP_SOUND_BUFSIZE - scsp_sound_genpos) this_count = SCSP_SOUND_BUFSIZE - scsp_sound_genpos; bufL = &scsp_buffer_L[scsp_sound_genpos]; bufR = &scsp_buffer_R[scsp_sound_genpos]; ScspGenerateAudio(bufL, bufR, this_count); scsp_sound_genpos += this_count; scsp_sound_left += this_count; sample_count -= this_count; } // Send audio to the output device if possible while (scsp_sound_left > 0 && (audio_free = SNDCore->GetAudioSpace()) > 0) { s32 out_start = (s32)scsp_sound_genpos - (s32)scsp_sound_left; if (out_start < 0) out_start += SCSP_SOUND_BUFSIZE; if (audio_free > scsp_sound_left) audio_free = scsp_sound_left; if (audio_free > SCSP_SOUND_BUFSIZE - out_start) audio_free = SCSP_SOUND_BUFSIZE - out_start; SNDCore->UpdateAudio((u32 *)&scsp_buffer_L[out_start], (u32 *)&scsp_buffer_R[out_start], audio_free); scsp_sound_left -= audio_free; #ifdef WIN32 ScspConvert32uto16s(&scsp_buffer_L[out_start], &scsp_buffer_R[out_start], (s16 *)stereodata16, audio_free); DRV_AviSoundUpdate(stereodata16, audio_free); #endif } } else // !scsp_frame_accurate { if ((audio_free = SNDCore->GetAudioSpace())) { if (audio_free > SCSP_SOUND_BUFSIZE) audio_free = SCSP_SOUND_BUFSIZE; ScspGenerateAudio(scsp_buffer_L, scsp_buffer_R, audio_free); SNDCore->UpdateAudio((u32 *)scsp_buffer_L, (u32 *)scsp_buffer_R, audio_free); #ifdef WIN32 ScspConvert32uto16s((s32 *)scsp_buffer_L, (s32 *)scsp_buffer_R, (s16 *)stereodata16, audio_free); DRV_AviSoundUpdate(stereodata16, audio_free); #endif } } // if (scsp_frame_accurate) // Update scsp_clock last, so the main thread can use it as a signal // that we've finished processing to this point scsp_clock += cycles; } //------------------------------------------------------------------------- // ScspTimerCyclesLeft: Return the approximate number of SCSP clock cycles // before an SCSP timer (A, B, or C) triggers an interrupt. static u32 ScspTimerCyclesLeft(u16 timer, u8 timer_scale) { return (0xFF00 - timer) << timer_scale; } //----------------------------------// // ScspUpdateTimer: Update an SCSP timer (A, B, or C) by the given number // of output samples, and raise an interrupt if the timer reaches 0xFF. static void ScspUpdateTimer(u32 samples, u16 *timer_ptr, u8 timer_scale, int interrupt) { u32 timer_new = *timer_ptr + (samples << (8 - timer_scale)); if (UNLIKELY(timer_new >= 0xFF00)) { ScspRaiseInterrupt(interrupt, SCSP_INTTARGET_BOTH); timer_new -= 0xFF00; // We won't pass 0xFF00 multiple times at once } *timer_ptr = timer_new; } //------------------------------------------------------------------------- // ScspGenerateAudio: Generate the given number of audio samples based on // the current SCSP state, and update the sound slot counters. static void ScspGenerateAudio(s32 *bufL, s32 *bufR, u32 samples) { int slotnum; u32 i; for (i = 0; i < samples; i++) bufL[i] = bufR[i] = 0; scsp_bufL = bufL; scsp_bufR = bufR; for (slotnum = 0; slotnum < 32; slotnum++) ScspGenerateAudioForSlot(&scsp.slot[slotnum], samples); if (cdda_next_out != PSP_UC(cdda_next_in) * 2352) { if (cdda_delay > 0) { if (samples > cdda_delay) { samples -= cdda_delay; cdda_delay = 0; } else { cdda_delay -= samples; samples = 0; } } if (cdda_delay == 0) ScspGenerateAudioForCDDA(bufL, bufR, samples); } if (cdda_next_out == PSP_UC(cdda_next_in) * 2352) cdda_delay = CDDA_DELAY_SAMPLES; // No data buffered, so reset delay } //----------------------------------// // ScspGenerateAudioForSlot: Generate audio samples and update counters for // a single slot. scsp_bufL and scsp_bufR are assumed to be set properly. static void ScspGenerateAudioForSlot(SlotState *slot, u32 samples) { if (slot->env_counter >= SCSP_ENV_DECAY_END) return; // No sound is currently playing (*slot->audiogen)(slot, samples); } //----------------------------------// // ScspGenerateAudioForCDDA: Generate audio samples for buffered CDDA data. static void ScspGenerateAudioForCDDA(s32 *bufL, s32 *bufR, u32 samples) { // May need to wrap around the buffer, so use nested loops while (samples > 0) { const u32 next_out = cdda_next_out; // Save volatile value locally const s32 temp = (PSP_UC(cdda_next_in) * 2352) - next_out; const u32 out_left = (temp < 0) ? sizeof(cdda_buf) - next_out : temp; const u32 this_len = (samples > out_left/4) ? out_left/4 : samples; const u8 *buf = &cdda_buf.data[next_out]; const u8 *top = buf + this_len*4; if (this_len == 0) break; // We ran out of buffered data for (; buf < top; buf += 4, bufL++, bufR++) { *bufL += (s32)(s16)((buf[1] << 8) | buf[0]); *bufR += (s32)(s16)((buf[3] << 8) | buf[2]); } if (next_out + this_len*4 >= sizeof(cdda_buf)) cdda_next_out = 0; else cdda_next_out = next_out + this_len*4; samples -= this_len; } } /////////////////////////////////////////////////////////////////////////// // SCSP register/memory access and I/O interface routines /////////////////////////////////////////////////////////////////////////// // SoundRam{Read,Write}{Byte,Word,Long}: Read or write sound RAM. // Intended for calling from external sources. u8 FASTCALL SoundRamReadByte(u32 address) { address &= scsp.sound_ram_mask; return T2ReadByte(SoundRam, address); } u16 FASTCALL SoundRamReadWord(u32 address) { address &= scsp.sound_ram_mask; return T2ReadWord(SoundRam, address); } u32 FASTCALL SoundRamReadLong(u32 address) { address &= scsp.sound_ram_mask; return T2ReadLong(SoundRam, address); } //----------------------------------// void FASTCALL SoundRamWriteByte(u32 address, u8 data) { address &= scsp.sound_ram_mask; T2WriteByte(SoundRam, address, data); M68K->WriteNotify(address, 1); } void FASTCALL SoundRamWriteWord(u32 address, u16 data) { address &= scsp.sound_ram_mask; T2WriteWord(SoundRam, address, data); M68K->WriteNotify(address, 2); } void FASTCALL SoundRamWriteLong(u32 address, u32 data) { address &= scsp.sound_ram_mask; T2WriteLong(SoundRam, address, data); M68K->WriteNotify(address, 4); } //------------------------------------------------------------------------- // Scsp{Read,Write}{Byte,Word,Long}: Read or write SCSP registers. // Intended for calling from external sources. u8 FASTCALL ScspReadByte(u32 address) { const u16 data = ScspReadWord(address & ~1); if (address & 1) return data & 0xFF; else return data >> 8; } u16 FASTCALL ScspReadWord(u32 address) { #ifdef PSP // Special handling for PSP cache management switch (address) { case 0x404: // MIDI in return 0xFF; // Not even supported, so don't bother trying case 0x408: // CA/SGC/EG return ScspReadMonitor(); default: return PSP_UC(scsp_regcache[address >> 1]); } #else return ScspReadWordDirect(address & 0xFFF); #endif } u32 FASTCALL ScspReadLong(u32 address) { return (u32)ScspReadWord(address) << 16 | ScspReadWord(address+2); } //----------------------------------// void FASTCALL ScspWriteByte(u32 address, u8 data) { if (scsp_thread_running) { PSP_UC(scsp_write_buffer_address) = address & 0xFFF; PSP_UC(scsp_write_buffer_data) = data; PSP_UC(scsp_write_buffer_size) = 1; while (PSP_UC(scsp_write_buffer_size) != 0) { YabThreadWake(YAB_THREAD_SCSP); YabThreadYield(); } return; } ScspWriteByteDirect(address & 0xFFF, data); } void FASTCALL ScspWriteWord(u32 address, u16 data) { if (scsp_thread_running) { PSP_UC(scsp_write_buffer_address) = address & 0xFFF; PSP_UC(scsp_write_buffer_data) = data; PSP_UC(scsp_write_buffer_size) = 2; while (PSP_UC(scsp_write_buffer_size) != 0) { YabThreadWake(YAB_THREAD_SCSP); YabThreadYield(); } return; } ScspWriteWordDirect(address & 0xFFF, data); } void FASTCALL ScspWriteLong(u32 address, u32 data) { if (scsp_thread_running) { PSP_UC(scsp_write_buffer_address) = address & 0xFFF; PSP_UC(scsp_write_buffer_data) = data; PSP_UC(scsp_write_buffer_size) = 4; while (PSP_UC(scsp_write_buffer_size) != 0) { YabThreadWake(YAB_THREAD_SCSP); YabThreadYield(); } return; } ScspWriteWordDirect(address & 0xFFF, data >> 16); ScspWriteWordDirect((address+2) & 0xFFF, data & 0xFFFF); } //------------------------------------------------------------------------- // ScspReceiveCDDA: Receive and buffer a sector (2352 bytes) of CDDA audio // data. Intended to be called by the CD driver when an audio sector has // been read in for playback. void ScspReceiveCDDA(const u8 *sector) { const u32 next_in = cdda_next_in; // Save volatile value locally const u32 next_next_in = (next_in + 1) % (sizeof(cdda_buf.sectors) / sizeof(cdda_buf.sectors[0])); // Make sure we have room for the new sector first const u32 next_out = PSP_UC(cdda_next_out); if (next_out > next_in * 2352 && next_out <= (next_in+1) * 2352) { SCSPLOG("WARNING: CDDA buffer overflow, discarding sector\n"); return; } memcpy(cdda_buf.sectors[next_in], sector, 2352); PSP_WRITEBACK_CACHE(cdda_buf.sectors[next_in], 2352); cdda_next_in = next_next_in; } /////////////////////////////////////////////////////////////////////////// // Miscellaneous SCSP interface routines /////////////////////////////////////////////////////////////////////////// // SoundSaveState: Save the current SCSP state to the given file. int SoundSaveState(FILE *fp) { int i; u32 temp; u8 temp8; int offset; IOCheck_struct check; if (scsp_thread_running) ScspSyncThread(); offset = StateWriteHeader(fp, "SCSP", 2); // Save 68k registers first ywrite(&check, (void *)&m68k_running, 1, 1, fp); for (i = 0; i < 8; i++) { temp = M68K->GetDReg(i); ywrite(&check, (void *)&temp, 4, 1, fp); } for (i = 0; i < 8; i++) { temp = M68K->GetAReg(i); ywrite(&check, (void *)&temp, 4, 1, fp); } temp = M68K->GetSR(); ywrite(&check, (void *)&temp, 4, 1, fp); temp = M68K->GetPC(); ywrite(&check, (void *)&temp, 4, 1, fp); // Now for the SCSP registers ywrite(&check, (void *)scsp_regcache, 0x1000, 1, fp); // Sound RAM is important ywrite(&check, (void *)SoundRam, 0x80000, 1, fp); // Write slot internal variables for (i = 0; i < 32; i++) { ywrite(&check, (void *)&scsp.slot[i].key, 1, 1, fp); ywrite(&check, (void *)&scsp.slot[i].addr_counter, 4, 1, fp); ywrite(&check, (void *)&scsp.slot[i].env_counter, 4, 1, fp); ywrite(&check, (void *)&scsp.slot[i].env_step, 4, 1, fp); ywrite(&check, (void *)&scsp.slot[i].env_target, 4, 1, fp); ywrite(&check, (void *)&scsp.slot[i].env_phase, 4, 1, fp); // Was enxt in scsp1; we don't use it, so just derive the proper // value from env_phase if (scsp.slot[i].env_phase == SCSP_ENV_RELEASE) temp8 = 1; else if (scsp.slot[i].env_phase == SCSP_ENV_SUSTAIN) temp8 = 2; else if (scsp.slot[i].env_phase == SCSP_ENV_DECAY) temp8 = 3; else if (scsp.slot[i].env_phase == SCSP_ENV_ATTACK) temp8 = 4; else // impossible, but avoid "undefined value" warnings temp8 = 0; ywrite(&check, (void *)&temp8, 1, 1, fp); ywrite(&check, (void *)&scsp.slot[i].lfo_counter, 4, 1, fp); ywrite(&check, (void *)&scsp.slot[i].lfo_step, 4, 1, fp); } // Write main internal variables // FIXME/SCSP1: need to write a lot of these from temporary variables // to maintain save state compatibility temp = scsp.mem4mb; ywrite(&check, (void *)&temp, 4, 1, fp); temp = scsp.mvol; ywrite(&check, (void *)&temp, 4, 1, fp); temp = scsp.rbl; ywrite(&check, (void *)&temp, 4, 1, fp); ywrite(&check, (void *)&scsp.rbp, 4, 1, fp); temp = scsp.mslc; ywrite(&check, (void *)&temp, 4, 1, fp); ywrite(&check, (void *)&scsp.dmea, 4, 1, fp); temp = scsp.drga; ywrite(&check, (void *)&temp, 4, 1, fp); temp = scsp.dgate<<6 | scsp.ddir<<5 | scsp.dexe<<4; ywrite(&check, (void *)&temp, 4, 1, fp); temp = scsp.dtlg; ywrite(&check, (void *)&temp, 4, 1, fp); ywrite(&check, (void *)scsp.midi_in_buf, 1, 4, fp); ywrite(&check, (void *)scsp.midi_out_buf, 1, 4, fp); ywrite(&check, (void *)&scsp.midi_in_cnt, 1, 1, fp); ywrite(&check, (void *)&scsp.midi_out_cnt, 1, 1, fp); temp8 = scsp.mofull<<4 | scsp.moemp<<3 | scsp.miovf<<2 | scsp.mifull<<1 | scsp.miemp<<0; ywrite(&check, (void *)&temp8, 1, 1, fp); temp = scsp.tima; ywrite(&check, (void *)&temp, 4, 1, fp); temp = scsp.tactl; ywrite(&check, (void *)&temp, 4, 1, fp); temp = scsp.timb; ywrite(&check, (void *)&temp, 4, 1, fp); temp = scsp.tbctl; ywrite(&check, (void *)&temp, 4, 1, fp); temp = scsp.timc; ywrite(&check, (void *)&temp, 4, 1, fp); temp = scsp.tcctl; ywrite(&check, (void *)&temp, 4, 1, fp); temp = scsp.scieb; ywrite(&check, (void *)&temp, 4, 1, fp); temp = scsp.scipd; ywrite(&check, (void *)&temp, 4, 1, fp); temp = scsp.scilv0; ywrite(&check, (void *)&temp, 4, 1, fp); temp = scsp.scilv1; ywrite(&check, (void *)&temp, 4, 1, fp); temp = scsp.scilv2; ywrite(&check, (void *)&temp, 4, 1, fp); temp = scsp.mcieb; ywrite(&check, (void *)&temp, 4, 1, fp); temp = scsp.mcipd; ywrite(&check, (void *)&temp, 4, 1, fp); ywrite(&check, (void *)scsp.stack, 4, 32 * 2, fp); return StateFinishHeader(fp, offset); } //------------------------------------------------------------------------- // SoundLoadState: Load the current SCSP state from the given file. int SoundLoadState(FILE *fp, int version, int size) { int i, i2; u32 temp; u8 temp8; IOCheck_struct check; if (scsp_thread_running) ScspSyncThread(); // Read 68k registers first yread(&check, (void *)&m68k_running, 1, 1, fp); for (i = 0; i < 8; i++) { yread(&check, (void *)&temp, 4, 1, fp); M68K->SetDReg(i, temp); } for (i = 0; i < 8; i++) { yread(&check, (void *)&temp, 4, 1, fp); M68K->SetAReg(i, temp); } yread(&check, (void *)&temp, 4, 1, fp); M68K->SetSR(temp); yread(&check, (void *)&temp, 4, 1, fp); M68K->SetPC(temp); // Now for the SCSP registers yread(&check, (void *)scsp_regcache, 0x1000, 1, fp); // And sound RAM yread(&check, (void *)SoundRam, 0x80000, 1, fp); // Break out slot registers into their respective fields for (i = 0; i < 32; i++) { for (i2 = 0; i2 < 0x18; i2 += 2) ScspWriteWordDirect(i<<5 | i2, scsp_regcache[(i<<5 | i2) >> 1]); // These are also called during writes, so they're not technically // necessary, but call them again anyway just to ensure everything's // up to date ScspUpdateSlotAddress(&scsp.slot[i]); ScspUpdateSlotFunc(&scsp.slot[i]); } if (version > 1) { // Read slot internal variables for (i = 0; i < 32; i++) { yread(&check, (void *)&scsp.slot[i].key, 1, 1, fp); yread(&check, (void *)&scsp.slot[i].addr_counter, 4, 1, fp); yread(&check, (void *)&scsp.slot[i].env_counter, 4, 1, fp); yread(&check, (void *)&scsp.slot[i].env_step, 4, 1, fp); yread(&check, (void *)&scsp.slot[i].env_target, 4, 1, fp); yread(&check, (void *)&scsp.slot[i].env_phase, 4, 1, fp); // Was enxt in scsp1; we don't use it, so just read and ignore yread(&check, (void *)&temp8, 1, 1, fp); yread(&check, (void *)&scsp.slot[i].lfo_counter, 4, 1, fp); yread(&check, (void *)&scsp.slot[i].lfo_step, 4, 1, fp); } // Read main internal variables yread(&check, (void *)&temp, 4, 1, fp); scsp.mem4mb = temp; // This one isn't saved in the state file (though it's not used anyway) scsp.dac18b = (scsp_regcache[0x400>>1] >> 8) & 1; yread(&check, (void *)&temp, 4, 1, fp); scsp.mvol = temp; yread(&check, (void *)&temp, 4, 1, fp); scsp.rbl = temp; yread(&check, (void *)&scsp.rbp, 4, 1, fp); yread(&check, (void *)&temp, 4, 1, fp); scsp.mslc = temp; yread(&check, (void *)&scsp.dmea, 4, 1, fp); yread(&check, (void *)&temp, 4, 1, fp); scsp.drga = temp; yread(&check, (void *)&temp, 4, 1, fp); scsp.dgate = temp>>6 & 1; scsp.ddir = temp>>5 & 1; scsp.dexe = temp>>4 & 1; yread(&check, (void *)&temp, 4, 1, fp); scsp.dtlg = temp; yread(&check, (void *)scsp.midi_in_buf, 1, 4, fp); yread(&check, (void *)scsp.midi_out_buf, 1, 4, fp); yread(&check, (void *)&scsp.midi_in_cnt, 1, 1, fp); yread(&check, (void *)&scsp.midi_out_cnt, 1, 1, fp); yread(&check, (void *)&temp8, 1, 1, fp); scsp.mofull = temp8>>4 & 1; scsp.moemp = temp8>>3 & 1; scsp.miovf = temp8>>2 & 1; scsp.mifull = temp8>>1 & 1; scsp.miemp = temp8>>0 & 1; yread(&check, (void *)&temp, 4, 1, fp); scsp.tima = temp; yread(&check, (void *)&temp, 4, 1, fp); scsp.tactl = temp; yread(&check, (void *)&temp, 4, 1, fp); scsp.timb = temp; yread(&check, (void *)&temp, 4, 1, fp); scsp.tbctl = temp; yread(&check, (void *)&temp, 4, 1, fp); scsp.timc = temp; yread(&check, (void *)&temp, 4, 1, fp); scsp.tcctl = temp; yread(&check, (void *)&temp, 4, 1, fp); scsp.scieb = temp; yread(&check, (void *)&temp, 4, 1, fp); scsp.scipd = temp; yread(&check, (void *)&temp, 4, 1, fp); scsp.scilv0 = temp; yread(&check, (void *)&temp, 4, 1, fp); scsp.scilv1 = temp; yread(&check, (void *)&temp, 4, 1, fp); scsp.scilv2 = temp; yread(&check, (void *)&temp, 4, 1, fp); scsp.mcieb = temp; yread(&check, (void *)&temp, 4, 1, fp); scsp.mcipd = temp; yread(&check, (void *)scsp.stack, 4, 32 * 2, fp); } if (scsp_thread_running) PSP_FLUSH_ALL(); return size; } //------------------------------------------------------------------------- // ScspSlotDebugStats: Generate a string describing the given slot's state // and store it in the passed-in buffer (which is assumed to be large enough // to hold the result). // Helper functions (defined below) static char *AddSoundLFO(char *outstring, const char *string, u16 level, u16 waveform); static char *AddSoundPan(char *outstring, u16 pan); static char *AddSoundLevel(char *outstring, u16 level); void ScspSlotDebugStats(u8 slotnum, char *outstring) { AddString(outstring, "Sound Source = "); switch (scsp.slot[slotnum].ssctl) { case 0: { AddString(outstring, "External DRAM data\r\n"); break; } case 1: { AddString(outstring, "Internal(Noise)\r\n"); break; } case 2: { AddString(outstring, "Internal(0's)\r\n"); break; } default: { AddString(outstring, "Invalid setting\r\n"); break; } } AddString(outstring, "Source bit = "); switch (scsp.slot[slotnum].sbctl) { case 0: { AddString(outstring, "No bit reversal\r\n"); break; } case 1: { AddString(outstring, "Reverse other bits\r\n"); break; } case 2: { AddString(outstring, "Reverse sign bit\r\n"); break; } case 3: { AddString(outstring, "Reverse sign and other bits\r\n"); break; } } // Loop Control AddString(outstring, "Loop Mode = "); switch (scsp.slot[slotnum].lpctl) { case 0: { AddString(outstring, "Off\r\n"); break; } case 1: { AddString(outstring, "Normal\r\n"); break; } case 2: { AddString(outstring, "Reverse\r\n"); break; } case 3: { AddString(outstring, "Alternating\r\n"); break; } } // PCM8B if (scsp.slot[slotnum].pcm8b) { AddString(outstring, "8-bit samples\r\n"); } else { AddString(outstring, "16-bit samples\r\n"); } AddString(outstring, "Start Address = %05lX\r\n", (unsigned long)scsp.slot[slotnum].sa); AddString(outstring, "Loop Start Address = %04X\r\n", scsp.slot[slotnum].lsa); AddString(outstring, "Loop End Address = %04X\r\n", scsp.slot[slotnum].lea); AddString(outstring, "Decay 1 Rate = %d\r\n", scsp.slot[slotnum].dr); AddString(outstring, "Decay 2 Rate = %d\r\n", scsp.slot[slotnum].sr); if (scsp.slot[slotnum].eghold) { AddString(outstring, "EG Hold Enabled\r\n"); } AddString(outstring, "Attack Rate = %d\r\n", scsp.slot[slotnum].ar); if (scsp.slot[slotnum].lpslnk) { AddString(outstring, "Loop Start Link Enabled\r\n"); } if (scsp.slot[slotnum].krs != 0) { AddString(outstring, "Key rate scaling = %d\r\n", scsp.slot[slotnum].krs); } AddString(outstring, "Decay Level = %d\r\n", scsp.slot[slotnum].sl); AddString(outstring, "Release Rate = %d\r\n", scsp.slot[slotnum].rr); if (scsp.slot[slotnum].stwinh) { AddString(outstring, "Stack Write Inhibited\r\n"); } if (scsp.slot[slotnum].sdir) { AddString(outstring, "Sound Direct Enabled\r\n"); } AddString(outstring, "Total Level = %d\r\n", scsp.slot[slotnum].tl); AddString(outstring, "Modulation Level = %d\r\n", scsp.slot[slotnum].mdl); AddString(outstring, "Modulation Input X = %d\r\n", scsp.slot[slotnum].mdx); AddString(outstring, "Modulation Input Y = %d\r\n", scsp.slot[slotnum].mdy); AddString(outstring, "Octave = %d\r\n", scsp.slot[slotnum].oct); AddString(outstring, "Frequency Number Switch = %d\r\n", scsp.slot[slotnum].fns); AddString(outstring, "LFO Reset = %s\r\n", scsp.slot[slotnum].lfore ? "TRUE" : "FALSE"); AddString(outstring, "LFO Frequency = %d\r\n", scsp.slot[slotnum].lfof); outstring = AddSoundLFO(outstring, "LFO Frequency modulation waveform =", scsp.slot[slotnum].plfos, scsp.slot[slotnum].plfows); AddString(outstring, "LFO Frequency modulation level = %d\r\n", scsp.slot[slotnum].plfos); outstring = AddSoundLFO(outstring, "LFO Amplitude modulation waveform =", scsp.slot[slotnum].alfos, scsp.slot[slotnum].alfows); AddString(outstring, "LFO Amplitude modulation level = %d\r\n", scsp.slot[slotnum].alfos); AddString(outstring, "Input mix level = "); outstring = AddSoundLevel(outstring, scsp.slot[slotnum].imxl); AddString(outstring, "Input Select = %d\r\n", scsp.slot[slotnum].isel); AddString(outstring, "Direct data send level = "); outstring = AddSoundLevel(outstring, scsp.slot[slotnum].disdl); AddString(outstring, "Direct data panpot = "); outstring = AddSoundPan(outstring, scsp.slot[slotnum].dipan); AddString(outstring, "Effect data send level = "); outstring = AddSoundLevel(outstring, scsp.slot[slotnum].efsdl); AddString(outstring, "Effect data panpot = "); outstring = AddSoundPan(outstring, scsp.slot[slotnum].efpan); } //----------------------------------// static char *AddSoundLFO(char *outstring, const char *string, u16 level, u16 waveform) { if (level > 0) { switch (waveform) { case 0: AddString(outstring, "%s Sawtooth\r\n", string); break; case 1: AddString(outstring, "%s Square\r\n", string); break; case 2: AddString(outstring, "%s Triangle\r\n", string); break; case 3: AddString(outstring, "%s Noise\r\n", string); break; } } return outstring; } //----------------------------------// static char *AddSoundPan(char *outstring, u16 pan) { if (pan == 0x0F) { AddString(outstring, "Left = -MAX dB, Right = -0 dB\r\n"); } else if (pan == 0x1F) { AddString(outstring, "Left = -0 dB, Right = -MAX dB\r\n"); } else { AddString(outstring, "Left = -%d dB, Right = -%d dB\r\n", (pan & 0xF) * 3, (pan >> 4) * 3); } return outstring; } //----------------------------------// static char *AddSoundLevel(char *outstring, u16 level) { if (level == 0) { AddString(outstring, "-MAX dB\r\n"); } else { AddString(outstring, "-%d dB\r\n", (7-level) * 6); } return outstring; } //------------------------------------------------------------------------- // ScspCommonControlRegisterDebugStats: Generate a string describing the // SCSP common state registers and store it in the passed-in buffer (which // is assumed to be large enough to hold the result). void ScspCommonControlRegisterDebugStats(char *outstring) { AddString(outstring, "Memory: %s\r\n", scsp.mem4mb ? "4 Mbit" : "2 Mbit"); AddString(outstring, "Master volume: %d\r\n", scsp.mvol); AddString(outstring, "Ring buffer length: %d\r\n", scsp.rbl); AddString(outstring, "Ring buffer address: %08lX\r\n", (unsigned long)scsp.rbp); AddString(outstring, "\r\n"); AddString(outstring, "Slot Status Registers\r\n"); AddString(outstring, "-----------------\r\n"); AddString(outstring, "Monitor slot: %d\r\n", scsp.mslc); AddString(outstring, "Call address: %d\r\n", (ScspReadWordDirect(0x408) >> 7) & 0xF); AddString(outstring, "\r\n"); AddString(outstring, "DMA Registers\r\n"); AddString(outstring, "-----------------\r\n"); AddString(outstring, "DMA memory address start: %08lX\r\n", (unsigned long)scsp.dmea); AddString(outstring, "DMA register address start: %03X\r\n", scsp.drga); AddString(outstring, "DMA Flags: %02X (%cDGATE %cDDIR %cDEXE)\r\n", scsp.dgate<<6 | scsp.ddir<<5 | scsp.dexe<<4, scsp.dgate ? '+' : '-', scsp.ddir ? '+' : '-', scsp.dexe ? '+' : '-'); AddString(outstring, "\r\n"); AddString(outstring, "Timer Registers\r\n"); AddString(outstring, "-----------------\r\n"); AddString(outstring, "Timer A counter: %02X\r\n", scsp.tima >> 8); AddString(outstring, "Timer A increment: Every %d sample(s)\r\n", 1 << scsp.tactl); AddString(outstring, "Timer B counter: %02X\r\n", scsp.timb >> 8); AddString(outstring, "Timer B increment: Every %d sample(s)\r\n", 1 << scsp.tbctl); AddString(outstring, "Timer C counter: %02X\r\n", scsp.timc >> 8); AddString(outstring, "Timer C increment: Every %d sample(s)\r\n", 1 << scsp.tcctl); AddString(outstring, "\r\n"); AddString(outstring, "Interrupt Registers\r\n"); AddString(outstring, "-----------------\r\n"); AddString(outstring, "Sound cpu interrupt pending: %04X\r\n", scsp.scipd); AddString(outstring, "Sound cpu interrupt enable: %04X\r\n", scsp.scieb); AddString(outstring, "Sound cpu interrupt level 0: %04X\r\n", scsp.scilv0); AddString(outstring, "Sound cpu interrupt level 1: %04X\r\n", scsp.scilv1); AddString(outstring, "Sound cpu interrupt level 2: %04X\r\n", scsp.scilv2); AddString(outstring, "Main cpu interrupt pending: %04X\r\n", scsp.mcipd); AddString(outstring, "Main cpu interrupt enable: %04X\r\n", scsp.mcieb); AddString(outstring, "\r\n"); } //------------------------------------------------------------------------- // ScspSlotDebugSaveRegisters: Write the values of a single slot's // registers to a file. int ScspSlotDebugSaveRegisters(u8 slotnum, const char *filename) { FILE *fp; int i; IOCheck_struct check; if ((fp = fopen(filename, "wb")) == NULL) return -1; for (i = (slotnum * 0x20); i < ((slotnum+1) * 0x20); i += 2) { #ifdef WORDS_BIGENDIAN ywrite(&check, (void *)&scsp_regcache[i], 1, 2, fp); #else ywrite(&check, (void *)&scsp_regcache[i+1], 1, 1, fp); ywrite(&check, (void *)&scsp_regcache[i], 1, 1, fp); #endif } fclose(fp); return 0; } //------------------------------------------------------------------------- // ScspSlotDebugAudioSaveWav: Generate audio for a single slot and save it // to a WAV file. // Helper function to generate audio (defined below) static u32 ScspSlotDebugAudio(SlotState *slot, s32 *workbuf, s16 *buf, u32 len); int ScspSlotDebugAudioSaveWav(u8 slotnum, const char *filename) { typedef struct { char id[4]; u32 size; } chunk_struct; typedef struct { chunk_struct riff; char rifftype[4]; } waveheader_struct; typedef struct { chunk_struct chunk; u16 compress; u16 numchan; u32 rate; u32 bytespersec; u16 blockalign; u16 bitspersample; } fmt_struct; s32 workbuf[512*2*2]; s16 buf[512*2]; SlotState slot; FILE *fp; u32 counter = 0; waveheader_struct waveheader; fmt_struct fmt; chunk_struct data; long length; IOCheck_struct check; if (scsp.slot[slotnum].lea == 0) return 0; if ((fp = fopen(filename, "wb")) == NULL) return -1; // Do wave header memcpy(waveheader.riff.id, "RIFF", 4); waveheader.riff.size = 0; // we'll fix this after the file is closed memcpy(waveheader.rifftype, "WAVE", 4); ywrite(&check, (void *)&waveheader, 1, sizeof(waveheader_struct), fp); // fmt chunk memcpy(fmt.chunk.id, "fmt ", 4); fmt.chunk.size = 16; // we'll fix this at the end fmt.compress = 1; // PCM fmt.numchan = 2; // Stereo fmt.rate = 44100; fmt.bitspersample = 16; fmt.blockalign = fmt.bitspersample / 8 * fmt.numchan; fmt.bytespersec = fmt.rate * fmt.blockalign; ywrite(&check, (void *)&fmt, 1, sizeof(fmt_struct), fp); // data chunk memcpy(data.id, "data", 4); data.size = 0; // we'll fix this at the end ywrite(&check, (void *)&data, 1, sizeof(chunk_struct), fp); memcpy(&slot, &scsp.slot[slotnum], sizeof(slot)); // Clear out the phase counter, etc. slot.addr_counter = 0; slot.env_counter = SCSP_ENV_ATTACK_START; slot.env_step = slot.env_step_a; slot.env_target = SCSP_ENV_ATTACK_END; slot.env_phase = SCSP_ENV_ATTACK; // Mix the audio, and then write it to the file for(;;) { if (ScspSlotDebugAudio(&slot, workbuf, buf, 512) == 0) break; counter += 512; ywrite(&check, (void *)buf, 2, 512 * 2, fp); if (slot.lpctl != 0 && counter >= (44100 * 2 * 5)) break; } length = ftell(fp); // Let's fix the riff chunk size and the data chunk size fseek(fp, sizeof(waveheader_struct)-0x8, SEEK_SET); length -= 0x4; ywrite(&check, (void *)&length, 1, 4, fp); fseek(fp, sizeof(waveheader_struct)+sizeof(fmt_struct)+0x4, SEEK_SET); length -= sizeof(waveheader_struct)+sizeof(fmt_struct); ywrite(&check, (void *)&length, 1, 4, fp); fclose(fp); return 0; } //----------------------------------// static u32 ScspSlotDebugAudio(SlotState *slot, s32 *workbuf, s16 *buf, u32 len) { s32 *bufL, *bufR; bufL = workbuf; bufR = workbuf+len; scsp_bufL = bufL; scsp_bufR = bufR; if (slot->env_counter >= SCSP_ENV_DECAY_END) return 0; // Not playing if (slot->ssctl) return 0; // not yet supported! memset(bufL, 0, sizeof(u32) * len); memset(bufR, 0, sizeof(u32) * len); ScspGenerateAudioForSlot(slot, len); ScspConvert32uto16s(bufL, bufR, buf, len); return len; } //------------------------------------------------------------------------- // ScspConvert32uto16s: Saturate two 32-bit input sample buffers to 16-bit // and interleave them into a single output buffer. void ScspConvert32uto16s(s32 *srcL, s32 *srcR, s16 *dest, u32 len) { u32 i; for (i = 0; i < len; i++, srcL++, srcR++, dest += 2) { // Left channel if (*srcL > 0x7FFF) dest[0] = 0x7FFF; else if (*srcL < -0x8000) dest[0] = -0x8000; else dest[0] = *srcL; // Right channel if (*srcR > 0x7FFF) dest[1] = 0x7FFF; else if (*srcR < -0x8000) dest[1] = -0x8000; else dest[1] = *srcR; } } /////////////////////////////////////////////////////////////////////////// // SCSP register read/write routines and internal helpers /////////////////////////////////////////////////////////////////////////// // Scsp{Read,Write}{Byte,Word}Direct: Perform an SCSP register read or // write. These are internal routines that implement the actual register // read/write logic. The address must be in the range [0,0xFFF]. static u8 FASTCALL ScspReadByteDirect(u32 address) { const u16 data = ScspReadWordDirect(address & ~1); if (address & 1) return data & 0xFF; else return data >> 8; } //----------------------------------// static u16 ScspReadMonitor(void) { u8 ca, sgc, eg; ca = (scsp.slot[scsp.mslc].addr_counter >> (SCSP_FREQ_LOW_BITS + 12)) & 0xF; switch (scsp.slot[scsp.mslc].env_phase) { case SCSP_ENV_ATTACK: sgc = 0; break; case SCSP_ENV_DECAY: sgc = 1; break; case SCSP_ENV_SUSTAIN: sgc = 2; break; case SCSP_ENV_RELEASE: sgc = 3; break; } eg = 0x1f - (scsp.slot[scsp.mslc].last_env >> 27); return (ca << 7) | (sgc << 5) | eg; } static u16 FASTCALL ScspReadWordDirect(u32 address) { switch (address) { case 0x404: // MIDI in return ScspMidiIn(); case 0x408: // CA/SGC/EG return ScspReadMonitor(); default: return PSP_UC(scsp_regcache[address >> 1]); } } //----------------------------------// static void FASTCALL ScspWriteByteDirect(u32 address, u8 data) { switch (address >> 8) { case 0x0: case 0x1: case 0x2: case 0x3: write_as_word: { // These can be treated as word writes, borrowing the missing // 8 bits from the register cache u16 word_data; if (address & 1) word_data = (PSP_UC(scsp_regcache[address >> 1]) & 0xFF00) | data; else word_data = (PSP_UC(scsp_regcache[address >> 1]) & 0x00FF) | (data << 8); ScspWriteWordDirect(address & ~1, word_data); return; } case 0x4: switch (address & 0xFF) { // FIXME: if interrupts are only triggered on 0->1 changes in // [SM]CIEB, we can skip 0x1E/0x1F/0x26/0x27 (see FIXME in // ScspWriteWordDirect()) case 0x1E: data &= 0x07; scsp.scieb = (data << 8) | (scsp.scieb & 0x00FF); ScspCheckInterrupts(0x700, SCSP_INTTARGET_SOUND); break; case 0x1F: scsp.scieb = (scsp.scieb & 0xFF00) | data; ScspCheckInterrupts(0x700, SCSP_INTTARGET_SOUND); break; case 0x20: return; // Not writable case 0x21: if (data & (1<<5)) ScspRaiseInterrupt(5, SCSP_INTTARGET_SOUND); return; // Not writable case 0x2A: data &= 0x07; scsp.mcieb = (data << 8) | (scsp.mcieb & 0x00FF); ScspCheckInterrupts(0x700, SCSP_INTTARGET_MAIN); break; case 0x2B: scsp.mcieb = (scsp.mcieb & 0xFF00) | data; ScspCheckInterrupts(0x700, SCSP_INTTARGET_MAIN); break; case 0x2C: return; // Not writable case 0x2D: if (data & (1<<5)) ScspRaiseInterrupt(5, SCSP_INTTARGET_MAIN); return; // Not writable case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2E: case 0x2F: goto write_as_word; default: goto unhandled_write; } break; default: unhandled_write: SCSPLOG("ScspWriteByteDirect(): unhandled write %02X to 0x%03X\n", data, address); break; } if (address & 1) { PSP_UC(scsp_regcache[address >> 1]) &= 0xFF00; PSP_UC(scsp_regcache[address >> 1]) |= data; } else { PSP_UC(scsp_regcache[address >> 1]) &= 0x00FF; PSP_UC(scsp_regcache[address >> 1]) |= data << 8; } } //----------------------------------// static void FASTCALL ScspWriteWordDirect(u32 address, u16 data) { switch (address >> 8) { case 0x0: case 0x1: case 0x2: case 0x3: { const int slotnum = (address & 0x3E0) >> 5; SlotState *slot = &scsp.slot[slotnum]; switch (address & 0x1F) { case 0x00: slot->key = (data >> 11) & 0x1; slot->sbctl = (data >> 9) & 0x3; slot->ssctl = (data >> 7) & 0x3; slot->lpctl = (data >> 5) & 0x3; slot->pcm8b = (data >> 4) & 0x1; slot->sa =((data >> 0) & 0xF) << 16 | (slot->sa & 0xFFFF); ScspUpdateSlotFunc(slot); if (slot->env_counter < SCSP_ENV_DECAY_END) ScspUpdateSlotAddress(slot); if (data & (1<<12)) ScspDoKeyOnOff(); data &= 0x0FFF; // Don't save KYONEX break; case 0x02: slot->sa = (slot->sa & 0xF0000) | data; if (slot->env_counter < SCSP_ENV_DECAY_END) ScspUpdateSlotAddress(slot); break; case 0x04: slot->lsa = data; if (slot->env_counter < SCSP_ENV_DECAY_END) ScspUpdateSlotAddress(slot); break; case 0x06: slot->lea = data; if (slot->env_counter < SCSP_ENV_DECAY_END) ScspUpdateSlotAddress(slot); break; case 0x08: slot->sr = (data >> 11) & 0x1F; slot->dr = (data >> 6) & 0x1F; slot->eghold = (data >> 5) & 0x1; slot->ar = (data >> 0) & 0x1F; ScspUpdateSlotEnv(slot); break; case 0x0A: data &= 0x7FFF; slot->lpslnk = (data >> 14) & 0x1; slot->krs = (data >> 10) & 0xF; slot->sl = (data >> 5) & 0x1F; slot->rr = (data >> 0) & 0x1F; if (slot->krs == 0xF) slot->krs_shift = 4; else slot->krs_shift = slot->krs >> 2; ScspUpdateSlotEnv(slot); slot->sl_target = (slot->sl << (5 + SCSP_ENV_LOW_BITS)) + SCSP_ENV_DECAY_START; break; case 0x0C: data &= 0x03FF; slot->stwinh = (data >> 9) & 0x1; slot->sdir = (data >> 8) & 0x1; slot->tl = (data >> 0) & 0xFF; slot->tl_mult = scsp_tl_table[slot->tl]; break; case 0x0E: slot->mdl = (data >> 12) & 0xF; slot->mdx = (data >> 6) & 0x3F; slot->mdy = (data >> 0) & 0x3F; break; case 0x10: data &= 0x7BFF; slot->oct = (data >> 11) & 0xF; slot->fns = (data >> 0) & 0x3FF; if (slot->oct & 8) slot->octave_shift = 23 - slot->oct; else slot->octave_shift = 7 - slot->oct; slot->addr_step = ((0x400 + slot->fns) << 7) >> slot->octave_shift; ScspUpdateSlotEnv(slot); break; case 0x12: slot->lfore = (data >> 15) & 0x1; slot->lfof = (data >> 10) & 0x1F; slot->plfows = (data >> 8) & 0x3; slot->plfos = (data >> 5) & 0x7; slot->alfows = (data >> 3) & 0x3; slot->alfos = (data >> 0) & 0x7; if (slot->lfore) { slot->lfo_step = -1; slot->lfo_counter = 0; slot->lfo_fm_shift = -1; slot->lfo_am_shift = -1; } else { slot->lfo_step = scsp_lfo_step[slot->lfof]; if (slot->plfos) slot->lfo_fm_shift = slot->plfos - 1; else slot->lfo_fm_shift = -1; if (slot->alfos) slot->lfo_am_shift = 11 - slot->alfos; else slot->lfo_am_shift = -1; } slot->lfo_fm_wave = scsp_lfo_wave_freq[slot->plfows]; slot->lfo_am_wave = scsp_lfo_wave_amp[slot->alfows]; ScspUpdateSlotFunc(slot); break; case 0x14: data &= 0x007F; slot->isel = (data >> 3) & 0xF; slot->imxl = (data >> 0) & 0x7; if (slot->imxl) slot->imxl_shift = (7 - slot->imxl) + SCSP_ENV_HIGH_BITS; else slot->imxl_shift = 31; break; case 0x16: slot->disdl = (data >> 13) & 0x7; slot->dipan = (data >> 8) & 0x1F; slot->efsdl = (data >> 5) & 0x7; slot->efpan = (data >> 0) & 0x1F; // Compute the output shift counts for the left and right // channels. If the direct sound output is muted, we assume // the data is being passed through the DSP (which we don't // currently implement) and take the effect output level // instead. Note that we lose 1 bit of resolution from the // panning parameter because we adjust the output level by // shifting (powers of two), while DIPAN/EFPAN have a // resolution of sqrt(2). if (slot->disdl) { slot->outshift_l = slot->outshift_r = (7 - slot->disdl) + SCSP_ENV_HIGH_BITS; if (slot->dipan & 0x10) // Pan left { if (slot->dipan == 0x1F) slot->outshift_r = 31; else slot->outshift_r += (slot->dipan >> 1) & 7; } else // Pan right { if (slot->dipan == 0xF) slot->outshift_l = 31; else slot->outshift_l += (slot->dipan >> 1) & 7; } } else if (slot->efsdl) { slot->outshift_l = slot->outshift_r = (7 - slot->efsdl) + SCSP_ENV_HIGH_BITS; if (slot->efpan & 0x10) // Pan left { if (slot->efpan == 0x1F) slot->outshift_r = 31; else slot->outshift_r += (slot->efpan >> 1) & 7; } else // Pan right { if (slot->efpan == 0xF) slot->outshift_l = 31; else slot->outshift_l += (slot->efpan >> 1) & 7; } } else slot->outshift_l = slot->outshift_r = 31; // Muted ScspUpdateSlotFunc(slot); break; default: goto unhandled_write; } break; } case 0x4: switch (address & 0xFF) { case 0x00: data &= 0x030F; // VER is hardwired data |= SCSP_VERSION << 4; scsp.mem4mb = (data >> 9) & 0x1; scsp.dac18b = (data >> 8) & 0x1; scsp.mvol = (data >> 0) & 0xF; if (scsp.mem4mb) M68K->SetFetch(0x000000, 0x080000, (pointer)SoundRam); else { M68K->SetFetch(0x000000, 0x040000, (pointer)SoundRam); M68K->SetFetch(0x040000, 0x080000, (pointer)SoundRam); M68K->SetFetch(0x080000, 0x0C0000, (pointer)SoundRam); M68K->SetFetch(0x0C0000, 0x100000, (pointer)SoundRam); } scsp.sound_ram_mask = scsp.mem4mb ? 0x7FFFF : 0x3FFFF; break; case 0x02: data &= 0x01FF; scsp.rbl = (data >> 7) & 0x3; scsp.rbp =((data >> 0) & 0x7F) << 13; break; case 0x04: return; // Not writable case 0x06: data &= 0x00FF; ScspMidiOut(data); break; case 0x08: data &= 0x7800; // CA/SGC/EG are not writable scsp.mslc = (data >> 11) & 0x1F; break; case 0x12: data &= 0xFFFE; scsp.dmea = (scsp.dmea & 0xF0000) | data; break; case 0x14: data &= 0xFFFE; scsp.dmea =((data >> 12) & 0xF) << 16 | (scsp.dmea & 0xFFFF); scsp.drga = (data >> 0) & 0xFFF; break; case 0x16: data &= 0x7FFE; scsp.dgate = (data >> 14) & 0x1; scsp.ddir = (data >> 13) & 0x1; scsp.dexe |= (data >> 12) & 0x1; // Writing 0 not allowed scsp.dtlg = (data >> 0) & 0xFFF; if (data & (1<<12)) ScspDoDMA(); break; case 0x18: data &= 0x07FF; scsp.tactl = (data >> 8) & 0x7; scsp.tima =((data >> 0) & 0xFF) << 8; break; case 0x1A: data &= 0x07FF; scsp.tbctl = (data >> 8) & 0x7; scsp.timb =((data >> 0) & 0xFF) << 8; break; case 0x1C: data &= 0x07FF; scsp.tcctl = (data >> 8) & 0x7; scsp.timc =((data >> 0) & 0xFF) << 8; break; case 0x1E: data &= 0x07FF; scsp.scieb = data; // FIXME: If a bit is already 1 in both SCIEB and SCIPD, // does writing another 1 here (no change) trigger another // interrupt or not? ScspCheckInterrupts(0x7FF, SCSP_INTTARGET_SOUND); break; case 0x20: if (data & (1<<5)) ScspRaiseInterrupt(5, SCSP_INTTARGET_SOUND); return; // Not writable case 0x22: ScspClearInterrupts(data, SCSP_INTTARGET_SOUND); return; // Not writable case 0x24: data &= 0x00FF; scsp.scilv0 = data; break; case 0x26: data &= 0x00FF; scsp.scilv1 = data; break; case 0x28: data &= 0x00FF; scsp.scilv2 = data; break; case 0x2A: data &= 0x07FF; scsp.mcieb = data; // FIXME: as above (SCIEB) ScspCheckInterrupts(0x7FF, SCSP_INTTARGET_MAIN); break; case 0x2C: if (data & (1<<5)) ScspRaiseInterrupt(5, SCSP_INTTARGET_MAIN); return; // Not writable case 0x2E: ScspClearInterrupts(data, SCSP_INTTARGET_MAIN); return; // Not writable default: goto unhandled_write; } break; default: unhandled_write: SCSPLOG("ScspWriteWordDirect(): unhandled write %04X to 0x%03X\n", data, address); break; } PSP_UC(scsp_regcache[address >> 1]) = data; } //------------------------------------------------------------------------- // ScspDoKeyOnOff: Apply the key-on/key-off setting for all slots. // Implements the KYONEX trigger. static void ScspDoKeyOnOff(void) { int slotnum; for (slotnum = 0; slotnum < 32; slotnum++) { if (scsp.slot[slotnum].key) ScspKeyOn(&scsp.slot[slotnum]); else ScspKeyOff(&scsp.slot[slotnum]); } } //----------------------------------// // ScspKeyOn: Execute a key-on event for a single slot. static void ScspKeyOn(SlotState *slot) { if (slot->env_phase != SCSP_ENV_RELEASE) return; // Can't key a sound that's already playing ScspUpdateSlotAddress(slot); slot->addr_counter = 0; slot->env_phase = SCSP_ENV_ATTACK; slot->env_counter = SCSP_ENV_ATTACK_START; // FIXME: should this start at the current value if the old sound is still decaying? slot->env_step = slot->env_step_a; slot->env_target = SCSP_ENV_ATTACK_END; } //----------------------------------// // ScspKeyOff: Execute a key-off event for a single slot. static void ScspKeyOff(SlotState *slot) { if (slot->env_phase == SCSP_ENV_RELEASE) return; // Can't release a sound that's already released // If we still are in attack phase at release time, convert attack to decay if (slot->env_phase == SCSP_ENV_ATTACK) slot->env_counter = SCSP_ENV_DECAY_END - slot->env_counter; slot->env_phase = SCSP_ENV_RELEASE; slot->env_step = slot->env_step_r; slot->env_target = SCSP_ENV_DECAY_END; } //------------------------------------------------------------------------- // ScspUpdateSlotAddress: Update the sample data pointer for the given // slot, bound slot->lea to the end of sound RAM, and cache the shifted // values of slot->lsa and slot->lea in slot->{lea,lsa}_shifted. static void ScspUpdateSlotAddress(SlotState *slot) { u32 max_samples; if (slot->pcm8b) slot->sa &= ~1; slot->sa &= scsp.sound_ram_mask; slot->buf = &SoundRam[slot->sa]; max_samples = scsp.sound_ram_mask - slot->sa; if (slot->pcm8b) max_samples >>= 1; if (slot->lsa > max_samples) slot->lsa = max_samples; slot->lsa_shifted = slot->lsa << SCSP_FREQ_LOW_BITS; if (slot->lea > max_samples) slot->lea = max_samples; slot->lea_shifted = ((slot->lea + 1) << SCSP_FREQ_LOW_BITS) - 1; slot->looplen_shifted = slot->lea_shifted - slot->lsa_shifted + 1; } //----------------------------------// // ScspUpdateSlotEnv: Update the envelope step values from the SR/DR/AR/RR // slot registers. slot->krs_shift and slot->octave_shift are assumed to // be up to date with respect to the KRS and OCT registers. static void ScspUpdateSlotEnv(SlotState *slot) { if (slot->sr) { const s32 *rate_table = &scsp_decay_rate[slot->sr << 1]; slot->env_step_s = rate_table[(15 - slot->octave_shift) >> slot->krs_shift]; } else slot->env_step_s = 0; if (slot->dr) { const s32 *rate_table = &scsp_decay_rate[slot->dr << 1]; slot->env_step_d = rate_table[(15 - slot->octave_shift) >> slot->krs_shift]; } else slot->env_step_d = 0; if (slot->ar) { const s32 *rate_table = &scsp_attack_rate[slot->ar << 1]; slot->env_step_a = rate_table[(15 - slot->octave_shift) >> slot->krs_shift]; } else slot->env_step_a = 0; if (slot->rr) { const s32 *rate_table = &scsp_decay_rate[slot->rr << 1]; slot->env_step_r = rate_table[(15 - slot->octave_shift) >> slot->krs_shift]; } else slot->env_step_r = 0; } //----------------------------------// // ScspUpdateSlotFunc: Update the audio generation function for the given // slot based on the slot's parameters. static void ScspUpdateSlotFunc(SlotState *slot) { if (slot->ssctl) // FIXME: noise (ssctl==1) not implemented slot->audiogen = scsp_audiogen_func_table[0][0][0][0][0]; else slot->audiogen = scsp_audiogen_func_table[slot->lfo_fm_shift >= 0] [slot->lfo_am_shift >= 0] [slot->pcm8b == 0] [slot->outshift_l != 31] [slot->outshift_r != 31]; } //------------------------------------------------------------------------- // ScspMidiIn: Handle a read from the MIDI input register ($404). Since // there is no facility for sending MIDI data (the Saturn does not have a // MIDI I/O port), most of this is essentially a giant no-op, but the logic // is included for reference. static u16 ScspMidiIn(void) { scsp.miovf = 0; scsp.mifull = 0; if (scsp.midi_in_cnt > 0) { scsp.mibuf = scsp.midi_in_buf[0]; scsp.midi_in_buf[0] = scsp.midi_in_buf[1]; scsp.midi_in_buf[1] = scsp.midi_in_buf[2]; scsp.midi_in_buf[2] = scsp.midi_in_buf[3]; scsp.midi_in_cnt--; scsp.miemp = (scsp.midi_in_cnt == 0); if (!scsp.miemp) ScspRaiseInterrupt(SCSP_INTERRUPT_MIDI_IN, SCSP_INTTARGET_BOTH); } else // scsp.midi_in_cnt == 0 scsp.mibuf = 0xFF; return scsp.mofull << 12 | scsp.moemp << 11 | scsp.miovf << 10 | scsp.mifull << 9 | scsp.miemp << 8 | scsp.mibuf << 0; } //----------------------------------// // ScspMidiOut: Handle a write to the MIDI output register ($406). static void ScspMidiOut(u8 data) { scsp.moemp = 0; if (scsp.midi_out_cnt < 4) scsp.midi_out_buf[scsp.midi_out_cnt++] = data; scsp.mofull = (scsp.midi_out_cnt >= 4); } //------------------------------------------------------------------------- // ScspDoDMA: Handle a DMA request (when 1 is written to DEXE). DMA is // processed instantaneously regardless of transfer size. static void ScspDoDMA(void) { const u32 dmea = scsp.dmea & scsp.sound_ram_mask; if (scsp.ddir) // {RAM,zero} -> registers { SCSPLOG("DMA %s RAM[$%05X] -> registers[$%03X]\n", scsp.dgate ? "clear" : "copy", dmea, scsp.drga); if (scsp.dgate) { u32 i; for (i = 0; i < scsp.dtlg; i += 2) ScspWriteWordDirect(scsp.drga + i, 0); } else { u32 i; for (i = 0; i < scsp.dtlg; i += 2) ScspWriteWordDirect(scsp.drga + i, T2ReadWord(SoundRam, dmea + i)); } } else // !scsp.ddir, i.e. registers -> RAM { SCSPLOG("DMA %s registers[$%03X] -> RAM[$%05X]\n", scsp.dgate ? "clear" : "copy", scsp.drga, dmea); if (scsp.dgate) memset(&SoundRam[dmea], 0, scsp.dtlg); else { u32 i; for (i = 0; i < scsp.dtlg; i += 2) T2WriteWord(SoundRam, dmea + i, ScspReadWordDirect(scsp.drga + i)); } M68K->WriteNotify(dmea, scsp.dtlg); } scsp.dexe = 0; PSP_UC(scsp_regcache[0x416>>1]) &= ~(1<<12); ScspRaiseInterrupt(SCSP_INTERRUPT_DMA, SCSP_INTTARGET_BOTH); } /////////////////////////////////////////////////////////////////////////// // Other SCSP internal helper routines /////////////////////////////////////////////////////////////////////////// // ScspSyncThread: Wait for the SCSP subthread to finish executing all // pending cycles. Do not call if the subthread is not running. static void ScspSyncThread(void) { PSP_FLUSH_ALL(); while (PSP_UC(scsp_clock) != scsp_clock_target) { YabThreadWake(YAB_THREAD_SCSP); YabThreadYield(); } } //------------------------------------------------------------------------- // ScspRaiseInterrupt: Raise an interrupt for the main and/or sound CPU. static void ScspRaiseInterrupt(int which, int target) { if (target & SCSP_INTTARGET_MAIN) { scsp.mcipd |= 1 << which; PSP_UC(scsp_regcache[0x42C >> 1]) = scsp.mcipd; if (scsp.mcieb & (1 << which)) { if (scsp_thread_running) PSP_UC(scsp_main_interrupt_pending) = 1; else (*scsp_interrupt_handler)(); } } if (target & SCSP_INTTARGET_SOUND) { scsp.scipd |= 1 << which; PSP_UC(scsp_regcache[0x420 >> 1]) = scsp.scipd; if (scsp.scieb & (1 << which)) { const int level_shift = (which > 7) ? 7 : which; const int level = ((scsp.scilv0 >> level_shift) & 1) << 0 | ((scsp.scilv1 >> level_shift) & 1) << 1 | ((scsp.scilv2 >> level_shift) & 1) << 2; M68K->SetIRQ(level); } } } //----------------------------------// // ScspCheckInterrupts: Check pending interrupts for the main or sound CPU // against the interrupt enable flags, and raise any interrupts which are // both enabled and pending. The mask parameter indicates which interrupts // should be checked. Implements writes to SCIEB and MCIEB. static void ScspCheckInterrupts(u16 mask, int target) { int i; for (i = 0; i < 11; i++) { if ((1<> 1]) = scsp.mcipd; } if (target & SCSP_INTTARGET_SOUND) { scsp.scipd &= ~mask; PSP_UC(scsp_regcache[0x420 >> 1]) = scsp.scipd; } } //------------------------------------------------------------------------- // ScspRunM68K: Run the M68K for the given number of cycles. static void ScspRunM68K(u32 cycles) { if (LIKELY(m68k_running)) { s32 new_cycles = m68k_saved_cycles + cycles; if (LIKELY(new_cycles > 0)) new_cycles -= (*m68k_execf)(new_cycles); m68k_saved_cycles = new_cycles; } } //----------------------------------// // M68KExecBP: Wrapper for M68K->Exec() which checks for breakpoints and // calls the breakpoint callback when one is reached. This logic is // extracted from M68KExec() to avoid unnecessary register spillage on the // fast (no-breakpoint) path. static s32 FASTCALL M68KExecBP(s32 cycles) { s32 cycles_to_exec = cycles; s32 cycles_executed = 0; int i; while (cycles_executed < cycles_to_exec) { // Make sure it isn't one of our breakpoints for (i = 0; i < m68k_num_breakpoints; i++) { if ((M68K->GetPC() == m68k_breakpoint[i].addr) && !m68k_in_breakpoint) { m68k_in_breakpoint = 1; if (M68KBreakpointCallback) M68KBreakpointCallback(m68k_breakpoint[i].addr); m68k_in_breakpoint = 0; } } // Execute instructions individually cycles_executed += M68K->Exec(1); } return cycles_executed; } /////////////////////////////////////////////////////////////////////////// // M68K management routines /////////////////////////////////////////////////////////////////////////// // M68KStart: Start the M68K processor running. void M68KStart(void) { if (scsp_thread_running) ScspSyncThread(); M68K->Reset(); m68k_saved_cycles = 0; m68k_running = 1; if (scsp_thread_running) PSP_FLUSH_ALL(); } //------------------------------------------------------------------------- // M68KStop: Halt the M68K processor. void M68KStop(void) { if (scsp_thread_running) ScspSyncThread(); m68k_running = 0; if (scsp_thread_running) PSP_FLUSH_ALL(); } //------------------------------------------------------------------------- // M68KStep: Execute a single M68K instruction. void M68KStep(void) { M68K->Exec(1); } //------------------------------------------------------------------------- // M68KWriteNotify: Notify the M68K emulator that a region of sound RAM // has been written to by an external agent. void M68KWriteNotify(u32 address, u32 size) { M68K->WriteNotify(address, size); } //------------------------------------------------------------------------- // M68KGetRegisters, M68KSetRegisters: Get or set the current values of // the M68K registers. void M68KGetRegisters(M68KRegs *regs) { int i; if (regs != NULL) { for (i = 0; i < 8; i++) { regs->D[i] = M68K->GetDReg(i); regs->A[i] = M68K->GetAReg(i); } regs->SR = M68K->GetSR(); regs->PC = M68K->GetPC(); } } void M68KSetRegisters(const M68KRegs *regs) { int i; if (regs != NULL) { for (i = 0; i < 8; i++) { M68K->SetDReg(i, regs->D[i]); M68K->SetAReg(i, regs->A[i]); } M68K->SetSR(regs->SR); M68K->SetPC(regs->PC); } } //------------------------------------------------------------------------- // M68KSetBreakpointCallback: Set a function to be called whenever an M68K // breakpoint is reached. void M68KSetBreakpointCallBack(void (*func)(u32 address)) { M68KBreakpointCallback = func; } //------------------------------------------------------------------------- // M68KAddCodeBreakpoint: Add an M68K breakpoint on the given address. // Returns 0 on success, -1 if the breakpoint table is full or there is // already a breakpoint set on the address. int M68KAddCodeBreakpoint(u32 address) { int i; if (m68k_num_breakpoints >= MAX_BREAKPOINTS) return -1; // Make sure it isn't already on the list for (i = 0; i < m68k_num_breakpoints; i++) { if (m68k_breakpoint[i].addr == address) return -1; } m68k_breakpoint[m68k_num_breakpoints].addr = address; m68k_num_breakpoints++; // Switch to the slow exec routine so we can catch the breakpoint m68k_execf = M68KExecBP; return 0; } //------------------------------------------------------------------------- // M68KDelCodeBreakpoint: Delete an M68K breakpoint on the given address. // Returns 0 on success, -1 if there was no breakpoint set on the address. int M68KDelCodeBreakpoint(u32 address) { int i; if (m68k_num_breakpoints > 0) { for (i = 0; i < m68k_num_breakpoints; i++) { if (m68k_breakpoint[i].addr == address) { // Swap with the last breakpoint in the table, so there are // no holes in the breakpoint list m68k_breakpoint[i].addr = m68k_breakpoint[m68k_num_breakpoints-1].addr; m68k_breakpoint[m68k_num_breakpoints-1].addr = 0xFFFFFFFF; m68k_num_breakpoints--; if (m68k_num_breakpoints == 0) { // Last breakpoint deleted, so go back to the fast exec routine m68k_execf = M68K->Exec; } return 0; } } } return -1; } //------------------------------------------------------------------------- // M68KGetBreakpointList: Return the array of breakpoints currently set. // The array is M68K_MAX_BREAKPOINTS elements long, and an address of // 0xFFFFFFFF indicates that no breakpoint is set in that slot. const M68KBreakpointInfo *M68KGetBreakpointList(void) { return m68k_breakpoint; } //------------------------------------------------------------------------- // M68KClearCodeBreakpoints: Clear all M68K breakpoints. void M68KClearCodeBreakpoints(void) { int i; for (i = 0; i < MAX_BREAKPOINTS; i++) m68k_breakpoint[i].addr = 0xFFFFFFFF; m68k_num_breakpoints = 0; m68k_execf = M68K->Exec; } //------------------------------------------------------------------------- // M68K{Read,Write}{Byte,Word}: Memory access routines for the M68K // emulation. Exported for use in debugging. u32 FASTCALL M68KReadByte(u32 address) { if (address < 0x100000) return T2ReadByte(SoundRam, address & scsp.sound_ram_mask); else return ScspReadByteDirect(address & 0xFFF); } u32 FASTCALL M68KReadWord(u32 address) { if (address < 0x100000) return T2ReadWord(SoundRam, address & scsp.sound_ram_mask); else return ScspReadWordDirect(address & 0xFFF); } void FASTCALL M68KWriteByte(u32 address, u32 data) { if (address < 0x100000) T2WriteByte(SoundRam, address & scsp.sound_ram_mask, data); else ScspWriteByteDirect(address & 0xFFF, data); } void FASTCALL M68KWriteWord(u32 address, u32 data) { if (address < 0x100000) T2WriteWord(SoundRam, address & scsp.sound_ram_mask, data); else ScspWriteWordDirect(address & 0xFFF, data); } ///////////////////////////////////////////////////////////////////////////