3561 lines
113 KiB
C
3561 lines
113 KiB
C
/* 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 <math.h>
|
|
#include <stdlib.h>
|
|
|
|
#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<<RBL words)
|
|
u32 rbp; // [6:0] Ring buffer pointer 19:13 (low bits are zero)
|
|
|
|
// $404
|
|
u8 mofull; // [12] MIDI output FIFO full flag
|
|
u8 moemp; // [11] MIDI output FIFO empty flag
|
|
u8 miovf; // [10] MIDI input FIFO overflow flag
|
|
u8 mifull; // [9] MIDI input FIFO full flag
|
|
u8 miemp; // [8] MIDI input FIFO empty flag
|
|
u8 mibuf; // [7:0] MIDI input data buffer
|
|
|
|
// $406
|
|
u8 mobuf; // [7:0] MIDI output data buffer
|
|
|
|
// $408
|
|
u8 mslc; // [15:11] Monitor slot
|
|
u8 ca; // [10:7] Call address
|
|
u8 sgc; // [6:5] Envelope phase
|
|
u8 eg; // [4:0] Envelope volume
|
|
|
|
// $40A..$410 unused (possibly used in the model 2 SCSP?)
|
|
|
|
// $412 // [15:1] DMA transfer start address 15:1
|
|
u32 dmea;
|
|
|
|
// $414
|
|
// [15:12] DMA transfer start address 19:16
|
|
u16 drga; // [11:1] DMA register address 11:1
|
|
|
|
// $416
|
|
u8 dgate; // [14] DMA gate (1 = zero-clear target)
|
|
u8 ddir; // [13] DMA direction (0 = from, 1 = to sound RAM)
|
|
u8 dexe; // [12] DMA execute (write 1 to start; returns to 0 when done)
|
|
u16 dtlg; // [11:1] DMA transfer length 11:1
|
|
|
|
// $418
|
|
u8 tactl; // [10:8] Timer A step divisor (step every 1<<tactl output samples)
|
|
u16 tima; // [7:0] Timer A counter (sends an IRQ at 0xFF), as 8.8 fixed point
|
|
|
|
// $41A
|
|
u8 tbctl; // [10:8] Timer B step divisor
|
|
u16 timb; // [7:0] Timer B counter, as 8.8 fixed point
|
|
|
|
// $41C
|
|
u8 tcctl; // [10:8] Timer C step divisor
|
|
u16 timc; // [7:0] Timer C counter, as 8.8 fixed point
|
|
|
|
// $41E
|
|
u16 scieb; // [10:0] Sound CPU interrupt enable
|
|
|
|
// $420
|
|
u16 scipd; // [10:0] Sound CPU interrupt pending
|
|
|
|
// $422
|
|
// scire; // [10:0] Sound CPU interrupt reset (not readable)
|
|
|
|
// $424
|
|
u8 scilv0; // [7:0] Sound CPU interrupt levels, bit 0
|
|
|
|
// $426
|
|
u8 scilv1; // [7:0] Sound CPU interrupt levels, bit 1
|
|
|
|
// $428
|
|
u8 scilv2; // [7:0] Sound CPU interrupt levels, bit 2
|
|
|
|
// $42A
|
|
u16 mcieb; // [10:0] Main CPU interrupt enable
|
|
|
|
// $42C
|
|
u16 mcipd; // [10:0] Main CPU interrupt pending
|
|
|
|
// $42E
|
|
// mcire; // [10:0] Main CPU interrupt reset (not readable)
|
|
|
|
////////////
|
|
// Internal state
|
|
|
|
s32 stack[32*2]; // 2 generations of sound output data ($600..$67E)
|
|
|
|
SlotState slot[32]; // Data for each slot
|
|
|
|
u32 sample_timer; // Sample output timer (in SCSP clocks, 256 = 1 sample)
|
|
|
|
u32 sound_ram_mask; // Sound RAM address mask (tracks mem4mb)
|
|
|
|
u8 midi_in_buf[4]; // MIDI in buffer
|
|
u8 midi_out_buf[4];// MIDI out buffer
|
|
u8 midi_in_cnt; // MIDI in data count
|
|
u8 midi_out_cnt; // MIDI out data count
|
|
|
|
} ScspState;
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Exported data
|
|
|
|
u8 *SoundRam;
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Lookup tables
|
|
|
|
// Attack/decay envelope lookup table
|
|
static s32 scsp_env_table[SCSP_ENV_LEN*2];
|
|
|
|
// LFO waveforms for amplitude modulation
|
|
static s32 scsp_lfo_wave_amp[4][SCSP_LFO_LEN];
|
|
// LFO waveforms for frequency modulation
|
|
static s32 scsp_lfo_wave_freq[4][SCSP_LFO_LEN];
|
|
// LFO counter step values for each LFOF index
|
|
static s32 scsp_lfo_step[32];
|
|
|
|
// Envelope increments for each attack/decay rate (AR, DR, etc.)
|
|
static s32 scsp_attack_rate[62+16];
|
|
static s32 scsp_decay_rate[62+16];
|
|
|
|
// Table of volume multipliers for TL (total level) register
|
|
static s32 scsp_tl_table[256];
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Other local data
|
|
|
|
//-------- Data written by main thread only --------//
|
|
|
|
// Cached fraction of a clock cycle (12.20 fixed point)
|
|
static u32 scsp_clock_frac;
|
|
// scsp_clock_frac increment per ScspExec(1) call
|
|
static u32 scsp_clock_inc;
|
|
|
|
// Selected sound output module
|
|
static SoundInterface_struct *SNDCore;
|
|
|
|
// Main CPU (SCU) interrupt function pointer
|
|
static void (*scsp_interrupt_handler)(void);
|
|
|
|
// Flag: Generate sound with frame-accurate timing?
|
|
static u8 scsp_frame_accurate;
|
|
|
|
//-------- Data used for inter-thread communication --------//
|
|
|
|
PSP_SECTION_START(sc_write)
|
|
PSP_SECTION_START(me_write)
|
|
|
|
// Core 11.2896MHz clock (continually counts up)
|
|
PSP_SECTION(me_write)
|
|
static volatile u32 scsp_clock;
|
|
// Target clock value for execution (execute until clock == clock_target)
|
|
PSP_SECTION(sc_write)
|
|
static volatile u32 scsp_clock_target;
|
|
|
|
// Flag: Is a subthread currently running? (Also used to signal the
|
|
// subthread to stop.)
|
|
PSP_SECTION(sc_write)
|
|
static volatile u8 scsp_thread_running;
|
|
|
|
// Flag: Has an interrupt request been generated for the main processor?
|
|
// (Set by the subthread, cleared by the main thread.)
|
|
PSP_SECTION(both_write)
|
|
static volatile u8 scsp_main_interrupt_pending;
|
|
|
|
// Buffer for external (SH-2) SCSP writes (only _size is written by both
|
|
// CPUs, but we keep all three in the same section for cache safety)
|
|
PSP_SECTION(both_write)
|
|
static volatile u8 scsp_write_buffer_size; // 0 = nothing buffered
|
|
PSP_SECTION(both_write)
|
|
static volatile u16 scsp_write_buffer_address;
|
|
PSP_SECTION(both_write)
|
|
static volatile u32 scsp_write_buffer_data;
|
|
|
|
// SCSP register value cache (caching handled separately)
|
|
#ifdef PSP
|
|
__attribute__((aligned(64)))
|
|
#endif
|
|
static u16 scsp_regcache[0x1000/2];
|
|
|
|
// CDDA input buffer and read/write pointers
|
|
PSP_SECTION(sc_write)
|
|
static union {
|
|
u8 sectors[CDDA_NUM_BUFFERS][2352];
|
|
u8 data[CDDA_NUM_BUFFERS*2352];
|
|
} cdda_buf;
|
|
PSP_SECTION(sc_write)
|
|
static volatile u32 cdda_next_in; // Offset of next _sector_ to store
|
|
PSP_SECTION(me_write)
|
|
static volatile u32 cdda_next_out; // Offset of next _byte_ to read out
|
|
|
|
PSP_SECTION_END(sc_write)
|
|
PSP_SECTION_END(me_write)
|
|
|
|
//--- Data written by SCSP thread only (except when thread is stopped) ---//
|
|
|
|
// Current SCSP state
|
|
static ScspState scsp;
|
|
|
|
// PCM output buffers and related data
|
|
static s32 scsp_buffer_L[SCSP_SOUND_BUFSIZE];
|
|
static s32 scsp_buffer_R[SCSP_SOUND_BUFSIZE];
|
|
static u32 scsp_sound_genpos; // Offset of next sample to generate
|
|
static u32 scsp_sound_left; // Samples not yet sent to host driver
|
|
|
|
// Parameters for audio data generation (these are file-scope to reduce
|
|
// parameter passing overhead)
|
|
static s32 *scsp_bufL; // Base pointer for left channel
|
|
static s32 *scsp_bufR; // Base pointer for right channel
|
|
|
|
// CDDA playback delay in samples (used to avoid audio popping when the
|
|
// SCSP emulation gets a few samples ahead of the CDDA input)
|
|
static u32 cdda_delay;
|
|
|
|
// M68K-related data
|
|
static u8 m68k_running; // Nonzero if M68K execution is enabled
|
|
static s32 FASTCALL (*m68k_execf)(s32 cycles); // M68K->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<<FREQ_LOW_BITS */ \
|
|
addr_counter += slot->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<<i) & mask & scsp.mcieb && scsp.mcipd)
|
|
ScspRaiseInterrupt(i, SCSP_INTTARGET_MAIN & target);
|
|
if ((1<<i) & mask & scsp.scieb && scsp.scipd)
|
|
ScspRaiseInterrupt(i, SCSP_INTTARGET_SOUND & target);
|
|
}
|
|
}
|
|
|
|
//----------------------------------//
|
|
|
|
// ScspClearInterrupts: Clear all pending interrupts specified by the mask
|
|
// parameter for the main or sound CPU. Implements writes to SCIRE and MCIRE.
|
|
|
|
static void ScspClearInterrupts(u16 mask, int target)
|
|
{
|
|
if (target & SCSP_INTTARGET_MAIN)
|
|
{
|
|
scsp.mcipd &= ~mask;
|
|
PSP_UC(scsp_regcache[0x42C >> 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);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|