mirror of
https://github.com/ekeeke/Genesis-Plus-GX.git
synced 2024-12-27 19:51:48 +01:00
1002 lines
32 KiB
C
1002 lines
32 KiB
C
/****************************************************************************
|
|
* Genesis Plus
|
|
* MegaSD flashcart CD hardware interface overlay & enhanced ROM mappers
|
|
*
|
|
* Copyright (C) 2020-2022 Eke-Eke (Genesis Plus GX)
|
|
*
|
|
* Redistribution and use of this code or any derivative works are permitted
|
|
* provided that the following conditions are met:
|
|
*
|
|
* - Redistributions may not be sold, nor may they be used in a commercial
|
|
* product or activity.
|
|
*
|
|
* - Redistributions that are modified from the original source must include the
|
|
* complete source code, including the source code for all components used by a
|
|
* binary built from the modified sources. However, as a special exception, the
|
|
* source code distributed need not include anything that is normally distributed
|
|
* (in either source or binary form) with the major components (compiler, kernel,
|
|
* and so on) of the operating system on which the executable runs, unless that
|
|
* component itself accompanies the executable.
|
|
*
|
|
* - Redistributions must reproduce the above copyright notice, this list of
|
|
* conditions and the following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************************/
|
|
#include "shared.h"
|
|
|
|
typedef struct
|
|
{
|
|
uint8 unlock;
|
|
uint8 bank0;
|
|
uint8 special;
|
|
uint8 writeEnable;
|
|
uint8 overlayEnable;
|
|
uint8 playbackLoop;
|
|
uint8 playbackLoopTrack;
|
|
uint8 playbackEndTrack;
|
|
uint16 result;
|
|
uint16 fadeoutStartVolume;
|
|
int fadeoutSamplesTotal;
|
|
int fadeoutSamplesCount;
|
|
int playbackSamplesCount;
|
|
int playbackLoopSector;
|
|
int playbackEndSector;
|
|
uint8 buffer[0x800];
|
|
} T_MEGASD_HW;
|
|
|
|
/* MegaSD mapper hardware */
|
|
static T_MEGASD_HW megasd_hw;
|
|
|
|
/* Internal function prototypes */
|
|
static void megasd_ctrl_write_byte(unsigned int address, unsigned int data);
|
|
static void megasd_ctrl_write_word(unsigned int address, unsigned int data);
|
|
static unsigned int megasd_ctrl_read_byte(unsigned int address);
|
|
static unsigned int megasd_ctrl_read_word(unsigned int address);
|
|
static void megasd_pcm_write_byte(unsigned int address, unsigned int data);
|
|
static void megasd_pcm_write_word(unsigned int address, unsigned int data);
|
|
static unsigned int megasd_pcm_read_byte(unsigned int address);
|
|
static unsigned int megasd_pcm_read_word(unsigned int address);
|
|
|
|
/* default MegaSD version & serial number */
|
|
static const unsigned char megasd_version[16] = {'M','E','G','A','S','D',0x01,0x04,0x07,0x00,0xFF,0xFF,0x12,0x34,0x56,0x78};
|
|
|
|
/* MegaSD ID string (NB: there seems to be an error in MegaSD Dev manual as given hexadecimal values correspond to ASCII word "BATE", not "RATE") */
|
|
static const unsigned char megasd_id[4] = {0x42,0x41,0x54,0x45};
|
|
|
|
void megasd_reset(void)
|
|
{
|
|
/* reset MegaSD mapper */
|
|
memset(&megasd_hw, 0x00, sizeof (megasd_hw));
|
|
|
|
/* cartridge area bank #7 default mapping */
|
|
megasd_hw.special = 0x07;
|
|
|
|
/* CD hardware access overlay is mapped in 0x030000-0x03FFFF */
|
|
m68k.memory_map[0x03].read8 = megasd_ctrl_read_byte;
|
|
m68k.memory_map[0x03].read16 = megasd_ctrl_read_word;
|
|
m68k.memory_map[0x03].write8 = megasd_ctrl_write_byte;
|
|
m68k.memory_map[0x03].write16 = megasd_ctrl_write_word;
|
|
zbank_memory_map[0x03].read = megasd_ctrl_read_byte;
|
|
|
|
/* reset CD hardware (only if not already emulated) */
|
|
if (system_hw != SYSTEM_MCD)
|
|
{
|
|
pcm_reset();
|
|
cdd_reset();
|
|
scd.regs[0x36>>1].byte.h = 0x01;
|
|
}
|
|
}
|
|
|
|
int megasd_context_save(uint8 *state)
|
|
{
|
|
int bufferptr = 0;
|
|
|
|
save_param(&megasd_hw, sizeof(megasd_hw));
|
|
|
|
/* save needed CD hardware state (only if not already saved) */
|
|
if (system_hw != SYSTEM_MCD)
|
|
{
|
|
bufferptr += cdd_context_save(&state[bufferptr]);
|
|
bufferptr += pcm_context_save(&state[bufferptr]);
|
|
save_param(&scd.regs[0x36>>1].byte.h, 1);
|
|
}
|
|
|
|
return bufferptr;
|
|
}
|
|
|
|
int megasd_context_load(uint8 *state)
|
|
{
|
|
int bufferptr = 0;
|
|
|
|
load_param(&megasd_hw, sizeof(megasd_hw));
|
|
|
|
/* load needed CD hardware state (only if not already loaded) */
|
|
if (system_hw != SYSTEM_MCD)
|
|
{
|
|
bufferptr += cdd_context_load(&state[bufferptr], STATE_VERSION);
|
|
bufferptr += pcm_context_load(&state[bufferptr]);
|
|
load_param(&scd.regs[0x36>>1].byte.h, 1);
|
|
}
|
|
|
|
return bufferptr;
|
|
}
|
|
|
|
/*
|
|
Enhanced "SSF2" mapper
|
|
*/
|
|
void megasd_enhanced_ssf2_mapper_w(unsigned int address, unsigned int data)
|
|
{
|
|
int i, updateLastBank = 0;
|
|
|
|
switch (address & 0xf)
|
|
{
|
|
case 0x0:
|
|
{
|
|
/* check protect bit */
|
|
if (data & 0x80)
|
|
{
|
|
/* access to bank #0 register and ROM write enable bit is unlocked */
|
|
megasd_hw.unlock = 1;
|
|
|
|
/* ROM write enable bit */
|
|
megasd_hw.writeEnable = data & 0x20;
|
|
|
|
/* map bank #0 selected ROM page to $000000-$07ffff */
|
|
for (i=0x00; i<0x08; i++)
|
|
{
|
|
m68k.memory_map[i].base = cart.rom + (((megasd_hw.bank0 & 0x0f) << 19) & cart.mask) + (i<<16);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* access to bank #0 register and ROM write enable bit is locked */
|
|
megasd_hw.unlock = 0;
|
|
|
|
/* disable ROM write enable access */
|
|
megasd_hw.writeEnable = 0;
|
|
|
|
/* reset default ROM mapping in $000000-$07ffff */
|
|
for (i=0x00; i<0x08; i++)
|
|
{
|
|
m68k.memory_map[i].base = cart.rom + (i<<16);
|
|
}
|
|
}
|
|
|
|
/* check ROM write enable status */
|
|
if (megasd_hw.writeEnable)
|
|
{
|
|
/* enable write access to cartridge ROM area ($000000-$37ffff) */
|
|
for (i=0x00; i<0x38; i++)
|
|
{
|
|
m68k.memory_map[i].write8 = NULL;
|
|
m68k.memory_map[i].write16 = NULL;
|
|
zbank_memory_map[i].write = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* disable ROM write access to cartridge ROM area ($000000-$37ffff) */
|
|
for (i=0x00; i<0x38; i++)
|
|
{
|
|
m68k.memory_map[i].write8 = m68k_unused_8_w;
|
|
m68k.memory_map[i].write16 = m68k_unused_16_w;
|
|
zbank_memory_map[i].write = zbank_unused_w;
|
|
}
|
|
|
|
/* enable CD hardware overlay area access (it is assumed that overlay area is disabled when ROM write access is enabled) */
|
|
m68k.memory_map[0x03].write8 = megasd_ctrl_write_byte;
|
|
m68k.memory_map[0x03].write16 = megasd_ctrl_write_word;
|
|
}
|
|
|
|
/* force $380000-$3fffff mapping update */
|
|
updateLastBank = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
case 0xf:
|
|
{
|
|
/* special bank register */
|
|
megasd_hw.special = data;
|
|
|
|
/* force $380000-$3fffff mapping update */
|
|
updateLastBank = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
/* LWR only */
|
|
if (address & 1)
|
|
{
|
|
/* 512K ROM paging (max. 8MB)*/
|
|
uint8 *src = cart.rom + (((data & 0x0f) << 19) & cart.mask);
|
|
|
|
/* cartridge area ($000000-$3FFFFF) is divided into 8 x 512K banks */
|
|
uint8 bank = (address << 2) & 0x38;
|
|
|
|
/* check selected bank is not locked */
|
|
if ((bank != 0x00) || megasd_hw.unlock)
|
|
{
|
|
/* selected ROM page is mapped to selected bank */
|
|
for (i=0; i<8; i++)
|
|
{
|
|
m68k.memory_map[bank + i].base = src + (i<<16);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m68k_unused_8_w(address, data);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* check if $380000-$3fffff mapping has to be updated */
|
|
if (updateLastBank)
|
|
{
|
|
/* check special bank register value */
|
|
if (megasd_hw.special == 0x80)
|
|
{
|
|
/* SRAM mapped in $380000-$3fffff (max 16KB) */
|
|
for (i=0x38; i<0x40; i++)
|
|
{
|
|
m68k.memory_map[i].base = sram.sram;
|
|
m68k.memory_map[i].read8 = sram_read_byte;
|
|
m68k.memory_map[i].read16 = sram_read_word;
|
|
m68k.memory_map[i].write8 = megasd_hw.writeEnable ? sram_write_byte : m68k_unused_8_w;
|
|
m68k.memory_map[i].write16 = megasd_hw.writeEnable ? sram_write_word : m68k_unused_16_w;
|
|
zbank_memory_map[i].read = sram_read_byte;
|
|
zbank_memory_map[i].write = megasd_hw.writeEnable ? sram_write_byte : zbank_unused_w;
|
|
}
|
|
}
|
|
else if (megasd_hw.special == 0x81)
|
|
{
|
|
/* PCM hardware mapped in $380000-$3fffff */
|
|
for (i=0x38; i<0x40; i++)
|
|
{
|
|
m68k.memory_map[i].base = NULL;
|
|
m68k.memory_map[i].read8 = megasd_pcm_read_byte;
|
|
m68k.memory_map[i].read16 = megasd_pcm_read_word;
|
|
m68k.memory_map[i].write8 = megasd_hw.writeEnable ? megasd_pcm_write_byte : m68k_unused_8_w;
|
|
m68k.memory_map[i].write16 = megasd_hw.writeEnable ? megasd_pcm_write_word : m68k_unused_16_w;
|
|
zbank_memory_map[i].read = megasd_pcm_read_byte;
|
|
zbank_memory_map[i].write = megasd_hw.writeEnable ? megasd_pcm_write_byte : zbank_unused_w;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* 512K ROM paging (max. 8MB)*/
|
|
uint8 *src = cart.rom + (((megasd_hw.special & 0x0f) << 19) & cart.mask);
|
|
|
|
/* selected ROM page mapped in $380000-$3fffff */
|
|
for (i=0x38; i<0x40; i++)
|
|
{
|
|
m68k.memory_map[i].base = src + (i << 16);;
|
|
m68k.memory_map[i].read8 = NULL;
|
|
m68k.memory_map[i].read16 = NULL;
|
|
m68k.memory_map[i].write8 = megasd_hw.writeEnable ? NULL : m68k_unused_8_w;
|
|
m68k.memory_map[i].write16 = megasd_hw.writeEnable ? NULL : m68k_unused_16_w;
|
|
zbank_memory_map[i].read = NULL;
|
|
zbank_memory_map[i].write = megasd_hw.writeEnable ? NULL : zbank_unused_w;;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
ROM write access mapper
|
|
*/
|
|
void megasd_rom_mapper_w(unsigned int address, unsigned int data)
|
|
{
|
|
int i;
|
|
|
|
if ((address & 0xff) == 0xff)
|
|
{
|
|
if (data == 'W')
|
|
{
|
|
/* enable write access to cartridge ROM area ($000000-$3fffff) */
|
|
for (i=0; i<0x40; i++)
|
|
{
|
|
m68k.memory_map[i].write8 = NULL;
|
|
m68k.memory_map[i].write16 = NULL;
|
|
zbank_memory_map[i].write = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* disable write access to cartridge ROM area ($000000-$3fffff) */
|
|
for (i=0; i<0x40; i++)
|
|
{
|
|
m68k.memory_map[i].write8 = m68k_unused_8_w;
|
|
m68k.memory_map[i].write16 = m68k_unused_16_w;
|
|
zbank_memory_map[i].write = zbank_unused_w;
|
|
}
|
|
|
|
/* enable CD hardware overlay access */
|
|
m68k.memory_map[0x03].write8 = megasd_ctrl_write_byte;
|
|
m68k.memory_map[0x03].write16 = megasd_ctrl_write_word;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m68k_unused_8_w(address, data);
|
|
}
|
|
}
|
|
|
|
/*
|
|
CDDA samples playback handler
|
|
*/
|
|
void megasd_update_cdda(unsigned int samples)
|
|
{
|
|
unsigned int count;
|
|
|
|
while (samples > 0)
|
|
{
|
|
/* check if audio playback is paused or stopped */
|
|
if (scd.regs[0x36>>1].byte.h == 0x01)
|
|
{
|
|
/* clear remaining needed CD-DA samples without updating counters */
|
|
cdd_read_audio(samples);
|
|
break;
|
|
}
|
|
|
|
/* attempt to read remaining needed samples by default */
|
|
count = samples;
|
|
|
|
/* check against fade out remaining samples */
|
|
if ((megasd_hw.fadeoutSamplesCount > 0) && (count > megasd_hw.fadeoutSamplesCount))
|
|
{
|
|
count = megasd_hw.fadeoutSamplesCount;
|
|
}
|
|
|
|
/* check against playback remaining samples */
|
|
if ((megasd_hw.playbackSamplesCount > 0) && (count > megasd_hw.playbackSamplesCount))
|
|
{
|
|
count = megasd_hw.playbackSamplesCount;
|
|
}
|
|
|
|
/* read required CD-DA samples */
|
|
cdd_read_audio(count);
|
|
|
|
/* adjust remaining needed samples count */
|
|
samples -= count;
|
|
|
|
/* check if fade out is still in progress */
|
|
if (megasd_hw.fadeoutSamplesCount > 0)
|
|
{
|
|
/* update remaining fade out samples count */
|
|
megasd_hw.fadeoutSamplesCount -= count;
|
|
|
|
/* check end of fade out */
|
|
if (megasd_hw.fadeoutSamplesCount <= 0)
|
|
{
|
|
/* pause audio playback */
|
|
scd.regs[0x36>>1].byte.h = 0x01;
|
|
cdd.status = CD_PAUSE;
|
|
|
|
/* restore initial volume */
|
|
cdd.fader[0] = cdd.fader[1] = megasd_hw.fadeoutStartVolume;
|
|
}
|
|
else
|
|
{
|
|
/* force volume for next frame */
|
|
cdd.fader[0] = cdd.fader[1] = (megasd_hw.fadeoutSamplesCount * megasd_hw.fadeoutStartVolume) / megasd_hw.fadeoutSamplesTotal;
|
|
}
|
|
}
|
|
|
|
/* Playback in progress ? */
|
|
if (megasd_hw.playbackSamplesCount > 0)
|
|
{
|
|
/* update remaining samples count */
|
|
megasd_hw.playbackSamplesCount -= count;
|
|
|
|
/* check end of current track */
|
|
if (megasd_hw.playbackSamplesCount <= 0)
|
|
{
|
|
/* check playback end track */
|
|
if (cdd.index < megasd_hw.playbackEndTrack)
|
|
{
|
|
/* seek start of next track */
|
|
cdd_seek_audio(cdd.index + 1, cdd.toc.tracks[cdd.index + 1].start);
|
|
|
|
/* increment current track index */
|
|
cdd.index++;
|
|
|
|
/* check if last track is being played */
|
|
if (cdd.index == megasd_hw.playbackEndTrack)
|
|
{
|
|
/* reinitialize remaining samples count using current track start sector and playback end sectors */
|
|
megasd_hw.playbackSamplesCount = (megasd_hw.playbackEndSector - cdd.toc.tracks[cdd.index].start) * 588;
|
|
}
|
|
else
|
|
{
|
|
/* reinitialize remaining samples count using current track start and end sectors */
|
|
megasd_hw.playbackSamplesCount = (cdd.toc.tracks[cdd.index].end - cdd.toc.tracks[cdd.index].start) * 588;
|
|
}
|
|
}
|
|
|
|
/* check track loop */
|
|
else if (megasd_hw.playbackLoop)
|
|
{
|
|
/* seek back to start track loop sector */
|
|
cdd_seek_audio(megasd_hw.playbackLoopTrack, megasd_hw.playbackLoopSector);
|
|
|
|
/* update current track index */
|
|
cdd.index = megasd_hw.playbackLoopTrack;
|
|
|
|
/* check if single track or successive tracks are being played */
|
|
if (cdd.index == megasd_hw.playbackEndTrack)
|
|
{
|
|
/* reinitialize remaining samples count using playback loop and end sectors */
|
|
megasd_hw.playbackSamplesCount = (megasd_hw.playbackEndSector - megasd_hw.playbackLoopSector) * 588;
|
|
}
|
|
else
|
|
{
|
|
/* reinitialize remaining samples count using playback loop sector and track end sector */
|
|
megasd_hw.playbackSamplesCount = (cdd.toc.tracks[cdd.index].end - megasd_hw.playbackLoopSector) * 588;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* stop audio playback */
|
|
cdd.status = CD_STOP;
|
|
scd.regs[0x36>>1].byte.h = 0x01;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
CD hardware overlay interface
|
|
*/
|
|
static void megasd_ctrl_write_byte(unsigned int address, unsigned int data)
|
|
{
|
|
/* check if overlay area access is enabled */
|
|
if (megasd_hw.overlayEnable)
|
|
{
|
|
/* 2KB buffer area */
|
|
if (address >= 0x03f800)
|
|
{
|
|
megasd_hw.buffer[address & 0x7ff] = data;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* cartridge area write access is disabled by default */
|
|
m68k_unused_8_w(address, data);
|
|
}
|
|
|
|
static void megasd_ctrl_write_word(unsigned int address, unsigned int data)
|
|
{
|
|
/* overlay port (word write only) */
|
|
if (address == 0x03f7fa)
|
|
{
|
|
/* enable /disable CD hardware overlay access */
|
|
megasd_hw.overlayEnable = (data == 0xcd54) ? 1 : 0;
|
|
return;
|
|
}
|
|
|
|
/* check if overlay area access is enabled */
|
|
if (megasd_hw.overlayEnable)
|
|
{
|
|
/* command port (word write only) */
|
|
if (address == 0x03f7fe)
|
|
{
|
|
switch ((data >> 8) & 0xff)
|
|
{
|
|
case 0x10: /* Get MegaSD version & serial number */
|
|
{
|
|
memcpy(megasd_hw.buffer, megasd_version, sizeof(megasd_version));
|
|
return;
|
|
}
|
|
|
|
case 0x11: /* Play specified CDDA track and stop */
|
|
case 0x12: /* Play specified CDDA track and loop from start */
|
|
case 0x1a: /* Play specified CDDA track and loop from offset */
|
|
{
|
|
/* check a valid disc is loaded */
|
|
if (cdd.loaded)
|
|
{
|
|
/* get track index from command parameter */
|
|
int index = (data & 0xff) - 1;
|
|
|
|
/* check track index corresponds to a valid audio track */
|
|
if ((index >= 0) && (index < cdd.toc.last) && !cdd.toc.tracks[index].type)
|
|
{
|
|
/* initialize default playback info */
|
|
megasd_hw.playbackEndTrack = index;
|
|
megasd_hw.playbackEndSector = cdd.toc.tracks[index].end;
|
|
|
|
/* seek audio track start */
|
|
cdd_seek_audio(index, cdd.toc.tracks[index].start);
|
|
|
|
/* update current track index */
|
|
cdd.index = index;
|
|
|
|
/* indicate audio track is playing */
|
|
cdd.status = CD_PLAY;
|
|
scd.regs[0x36>>1].byte.h = 0x00;
|
|
|
|
/* check if fade out is still in progress */
|
|
if (megasd_hw.fadeoutSamplesCount > 0)
|
|
{
|
|
/* reset fade out */
|
|
megasd_hw.fadeoutSamplesCount = 0;
|
|
|
|
/* restore initial volume */
|
|
cdd.fader[0] = cdd.fader[1] = megasd_hw.fadeoutStartVolume;
|
|
}
|
|
|
|
/* initialize remaining samples count */
|
|
megasd_hw.playbackSamplesCount = (cdd.toc.tracks[index].end - cdd.toc.tracks[index].start) * 588;
|
|
|
|
/* check if there are any loop commands found in cue file for this track (see cdd_load function in cdd.c) */
|
|
if (cdd.toc.tracks[index].loopEnabled != 0)
|
|
{
|
|
/* cue file loop commands override programmed command loop settings */
|
|
megasd_hw.playbackLoop = cdd.toc.tracks[index].loopEnabled + 1;
|
|
megasd_hw.playbackLoopSector = cdd.toc.tracks[index].start + cdd.toc.tracks[index].loopOffset;
|
|
}
|
|
else
|
|
{
|
|
/* track loop is enabled for commands 12h and 1Ah / disabled for command 11h */
|
|
megasd_hw.playbackLoop = (data >> 8) & 0x02;
|
|
|
|
/* command 1Ah specifies track loop offset in data buffer (32-bit value stored in big-endian format) */
|
|
if ((data >> 8) == 0x1a)
|
|
{
|
|
#ifndef LSB_FIRST
|
|
megasd_hw.playbackLoopSector = cdd.toc.tracks[index].start + *(unsigned int *)(megasd_hw.buffer);
|
|
#else
|
|
megasd_hw.playbackLoopSector = cdd.toc.tracks[index].start + (megasd_hw.buffer[0] << 24) + (megasd_hw.buffer[1] << 16) + (megasd_hw.buffer[2] << 8) + megasd_hw.buffer[3];
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
/* default track loop offset */
|
|
megasd_hw.playbackLoopSector = cdd.toc.tracks[index].start;
|
|
}
|
|
}
|
|
|
|
/* check loop sector is within track limit */
|
|
if ((megasd_hw.playbackLoopSector < cdd.toc.tracks[index].start) || (megasd_hw.playbackLoopSector >= cdd.toc.tracks[index].end))
|
|
{
|
|
/* force default track loop offset */
|
|
megasd_hw.playbackLoopSector = cdd.toc.tracks[index].start;
|
|
}
|
|
|
|
/* initialize loop track index */
|
|
megasd_hw.playbackLoopTrack = index;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 0x13: /* Pause CDDA track */
|
|
{
|
|
/* check audio track is currently playing */
|
|
if (scd.regs[0x36>>1].byte.h == 0x00)
|
|
{
|
|
/* check if fade out is already in progress */
|
|
if (megasd_hw.fadeoutSamplesCount > 0)
|
|
{
|
|
/* restore initial volume */
|
|
cdd.fader[0] = cdd.fader[1] = megasd_hw.fadeoutStartVolume;
|
|
}
|
|
|
|
/* get fade out samples count from command parameter */
|
|
megasd_hw.fadeoutSamplesCount = (data & 0xff) * 588;
|
|
|
|
/* fade out enabled ? */
|
|
if (megasd_hw.fadeoutSamplesCount > 0)
|
|
{
|
|
/* save fade out sample count */
|
|
megasd_hw.fadeoutSamplesTotal = megasd_hw.fadeoutSamplesCount;
|
|
|
|
/* save current volume */
|
|
megasd_hw.fadeoutStartVolume = cdd.fader[0];
|
|
}
|
|
else
|
|
{
|
|
/* pause audio track playback immediately when fade out is disabled */
|
|
scd.regs[0x36>>1].byte.h = 0x01;
|
|
cdd.status = CD_PAUSE;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 0x14: /* Resume CDDA track */
|
|
{
|
|
/* check audio playback is currently paused */
|
|
if (cdd.status == CD_PAUSE)
|
|
{
|
|
scd.regs[0x36>>1].byte.h = 0x00;
|
|
cdd.status = CD_PLAY;
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 0x15: /* Set CDDA volume (0-255) */
|
|
{
|
|
/* check if fade out is in progress */
|
|
if (megasd_hw.fadeoutSamplesCount > 0)
|
|
{
|
|
/* update default volume to be restored once fade out is finished */
|
|
megasd_hw.fadeoutStartVolume = ((data & 0xff) * 0x400) / 255;
|
|
}
|
|
else
|
|
{
|
|
/* update current volume */
|
|
cdd.fader[0] = cdd.fader[1] = ((data & 0xff) * 0x400) / 255;
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 0x16: /* Get CDDA playback status */
|
|
{
|
|
megasd_hw.result = (scd.regs[0x36>>1].byte.h == 0x00) ? 0x01 : 0x00;
|
|
return;
|
|
}
|
|
|
|
case 0x17: /* Request CD sector read */
|
|
{
|
|
/* check a disc with valid data track is loaded */
|
|
if (cdd.loaded && cdd.toc.tracks[0].type)
|
|
{
|
|
/* get LBA from command buffer (32-bit value stored in big-endian format) */
|
|
#ifndef LSB_FIRST
|
|
int lba = *(unsigned int *)(megasd_hw.buffer) - 150;
|
|
#else
|
|
int lba = (megasd_hw.buffer[0] << 24) + (megasd_hw.buffer[1] << 16) + (megasd_hw.buffer[2] << 8) + megasd_hw.buffer[3] - 150;
|
|
#endif
|
|
/* only allow reading within first data track */
|
|
if (lba < cdd.toc.tracks[0].end)
|
|
{
|
|
/* set current LBA position */
|
|
cdd.lba = lba;
|
|
|
|
/* set current track index */
|
|
cdd.index = 0;
|
|
|
|
/* update CDD status to allow reading data track */
|
|
cdd.status = CD_PLAY;
|
|
|
|
/* no audio track playing */
|
|
scd.regs[0x36>>1].byte.h = 0x01;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 0x18: /* Transfer last read sector */
|
|
{
|
|
/* check a disc is loaded with a valid data track currently being read */
|
|
if (cdd.loaded && (cdd.status == CD_PLAY) && cdd.toc.tracks[cdd.index].type)
|
|
{
|
|
/* read sector data to 2K buffer */
|
|
cdd_read_data(megasd_hw.buffer, NULL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 0x19: /* Request read of next sector */
|
|
{
|
|
/* check a disc is loaded with a valid data track currently being read */
|
|
if (cdd.loaded && (cdd.status == CD_PLAY) && cdd.toc.tracks[cdd.index].type)
|
|
{
|
|
/* only allow reading within first data track */
|
|
if (cdd.lba < (cdd.toc.tracks[0].end - 1))
|
|
{
|
|
/* increment current LBA position */
|
|
cdd.lba++;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 0x1b: /* Play CDDA from specific sector */
|
|
{
|
|
/* check a valid disc is loaded */
|
|
if (cdd.loaded)
|
|
{
|
|
/* get playback start sector from command buffer (32-bit value in big-endian format) */
|
|
#ifndef LSB_FIRST
|
|
int lba = *(unsigned int *)(megasd_hw.buffer) - 150;
|
|
#else
|
|
int lba = (megasd_hw.buffer[0] << 24) + (megasd_hw.buffer[1] << 16) + (megasd_hw.buffer[2] << 8) + megasd_hw.buffer[3] - 150;
|
|
#endif
|
|
|
|
/* get playback start track index */
|
|
int index = 0;
|
|
while ((cdd.toc.tracks[index].end <= lba) && (index < cdd.toc.last))
|
|
{
|
|
index++;
|
|
}
|
|
|
|
/* check start track index corresponds to a valid audio track */
|
|
if ((index < cdd.toc.last) && (cdd.toc.tracks[index].type == 0x00))
|
|
{
|
|
/* stay within track limits when seeking files */
|
|
if (lba < cdd.toc.tracks[index].start)
|
|
{
|
|
lba = cdd.toc.tracks[index].start;
|
|
}
|
|
|
|
/* seek audio track start offset */
|
|
cdd_seek_audio(index, lba);
|
|
|
|
/* update current track index */
|
|
cdd.index = index;
|
|
|
|
/* indicate audio track is playing */
|
|
cdd.status = CD_PLAY;
|
|
scd.regs[0x36>>1].byte.h = 0x00;
|
|
|
|
/* check if fade out is still in progress */
|
|
if (megasd_hw.fadeoutSamplesCount > 0)
|
|
{
|
|
/* reset fade out */
|
|
megasd_hw.fadeoutSamplesCount = 0;
|
|
|
|
/* restore initial volume */
|
|
cdd.fader[0] = cdd.fader[1] = megasd_hw.fadeoutStartVolume;
|
|
}
|
|
|
|
/* get playback end sector from command buffer (32-bit value in big-endian format) */
|
|
#ifndef LSB_FIRST
|
|
megasd_hw.playbackEndSector = *(unsigned int *)(megasd_hw.buffer + 4) - 150;
|
|
#else
|
|
megasd_hw.playbackEndSector = (megasd_hw.buffer[4] << 24) + (megasd_hw.buffer[5] << 16) + (megasd_hw.buffer[6] << 8) + megasd_hw.buffer[7] - 150;
|
|
#endif
|
|
|
|
/* check playback end sector is greater than start sector */
|
|
if (megasd_hw.playbackEndSector > lba)
|
|
{
|
|
/* get playback end track index */
|
|
megasd_hw.playbackEndTrack = index;
|
|
while ((cdd.toc.tracks[megasd_hw.playbackEndTrack].end <= megasd_hw.playbackEndSector) && (megasd_hw.playbackEndTrack < cdd.toc.last))
|
|
{
|
|
megasd_hw.playbackEndTrack++;
|
|
}
|
|
|
|
/* force playback end sector to end of last track when greater than last disc sector */
|
|
if (megasd_hw.playbackEndTrack == cdd.toc.last)
|
|
{
|
|
megasd_hw.playbackEndSector = cdd.toc.tracks[cdd.toc.last - 1].end;
|
|
megasd_hw.playbackEndTrack = cdd.toc.last - 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* force playback end sector to end of playback start track otherwise */
|
|
megasd_hw.playbackEndSector = cdd.toc.tracks[index].end;
|
|
megasd_hw.playbackEndTrack = index;
|
|
}
|
|
|
|
/* check if a single track or successive tracks are played */
|
|
if (megasd_hw.playbackEndTrack == index)
|
|
{
|
|
/* initialize remaining samples count using playback start and end sectors */
|
|
megasd_hw.playbackSamplesCount = (megasd_hw.playbackEndSector - lba) * 588;
|
|
}
|
|
else
|
|
{
|
|
/* initialize remaining samples count using playback start sector and start track last sector */
|
|
megasd_hw.playbackSamplesCount = (cdd.toc.tracks[index].end - lba) * 588;
|
|
}
|
|
|
|
/* check if there are any loop commands found in cue file for this track (see cdd_load function in cdd.c) */
|
|
if (cdd.toc.tracks[index].loopEnabled != 0)
|
|
{
|
|
/* cue file loop commands override programmed command loop settings */
|
|
megasd_hw.playbackLoop = cdd.toc.tracks[index].loopEnabled + 1;
|
|
megasd_hw.playbackLoopSector = cdd.toc.tracks[index].start + cdd.toc.tracks[index].loopOffset;
|
|
}
|
|
else
|
|
{
|
|
/* get track loop setting from command parameter */
|
|
megasd_hw.playbackLoop = data & 0x01;
|
|
|
|
/* track loop enabled ? */
|
|
if (megasd_hw.playbackLoop)
|
|
{
|
|
/* get playback loop sector from data buffer (32-bit value in big-endian format) */
|
|
#ifndef LSB_FIRST
|
|
megasd_hw.playbackLoopSector = *(unsigned int *)(megasd_hw.buffer + 8) - 150;
|
|
#else
|
|
megasd_hw.playbackLoopSector = (megasd_hw.buffer[8] << 24) + (megasd_hw.buffer[9] << 16) + (megasd_hw.buffer[10] << 8) + megasd_hw.buffer[11] - 150;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
/* set default track loop offset */
|
|
megasd_hw.playbackLoopSector = cdd.toc.tracks[index].start;
|
|
}
|
|
}
|
|
|
|
/* check loop sector is within start track limits */
|
|
if ((megasd_hw.playbackLoopSector < cdd.toc.tracks[index].start) || (megasd_hw.playbackLoopSector >= cdd.toc.tracks[index].end) || (megasd_hw.playbackLoopSector >= megasd_hw.playbackEndSector))
|
|
{
|
|
/* force default track loop offset */
|
|
megasd_hw.playbackLoopSector = cdd.toc.tracks[index].start;
|
|
}
|
|
|
|
/* initialize loop track index */
|
|
megasd_hw.playbackLoopTrack = index;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 0x1C: /* Start reading from a file */
|
|
case 0x1D: /* Read data from file to ROM */
|
|
case 0x1E: /* Read directory files to ROM */
|
|
case 0x1F: /* Play WAV file */
|
|
case 0x20: /* Read 2K data block from file to internal buffer */
|
|
case 0x21: /* Read next 2K data block from file to internal buffer */
|
|
{
|
|
/* unsupported commands */
|
|
megasd_hw.result = 0;
|
|
return;
|
|
}
|
|
|
|
default:
|
|
{
|
|
/* invalid command */
|
|
m68k_unused_16_w(address, data);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 2KB buffer area */
|
|
if (address >= 0x03f800)
|
|
{
|
|
WRITE_WORD(megasd_hw.buffer, address & 0x7fe, data);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* cartridge area write access is disabled by default */
|
|
m68k_unused_16_w(address, data);
|
|
}
|
|
|
|
static unsigned int megasd_ctrl_read_byte(unsigned int address)
|
|
{
|
|
/* check if overlay area access is enabled */
|
|
if (megasd_hw.overlayEnable)
|
|
{
|
|
/* ID port */
|
|
if ((address >= 0x3f7f6) && (address <= 0x3f7f9))
|
|
{
|
|
return megasd_id[address & 0x03];
|
|
}
|
|
|
|
/* Overlay port */
|
|
if ((address >= 0x3f7fa) && (address <= 0x3f7fb))
|
|
{
|
|
return (address & 1) ? 0x54 :0xcd;
|
|
}
|
|
|
|
/* Result port */
|
|
if ((address >= 0x3f7fc) && (address <= 0x3f7fd))
|
|
{
|
|
return (address & 1) ? (megasd_hw.result & 0xff) : (megasd_hw.result >> 8);
|
|
}
|
|
|
|
/* Command port */
|
|
if ((address >= 0x3f7fe) && (address <= 0x3f7ff))
|
|
{
|
|
/* commands processing time is not emulated */
|
|
return 0x00;
|
|
}
|
|
|
|
/* 2KB buffer area */
|
|
if (address >= 0x03f800)
|
|
{
|
|
return megasd_hw.buffer[address & 0x7ff];
|
|
}
|
|
}
|
|
|
|
/* default cartridge area */
|
|
return READ_BYTE(m68k.memory_map[0x03].base, address & 0xffff);
|
|
}
|
|
|
|
static unsigned int megasd_ctrl_read_word(unsigned int address)
|
|
{
|
|
/* check if overlay area access is enabled */
|
|
if (megasd_hw.overlayEnable)
|
|
{
|
|
/* ID port */
|
|
if ((address == 0x3f7f6) || (address == 0x3f7f8))
|
|
{
|
|
return READ_WORD(megasd_id, address - 0x3f7f6);
|
|
}
|
|
|
|
/* Overlay port */
|
|
if (address == 0x3f7fa)
|
|
{
|
|
return 0xcd54;
|
|
}
|
|
|
|
/* Result port */
|
|
if (address == 0x3f7fc)
|
|
{
|
|
return megasd_hw.result;
|
|
}
|
|
|
|
/* Command port */
|
|
if (address == 0x3f7fe)
|
|
{
|
|
/* commands processing time is not emulated */
|
|
return 0x0000;
|
|
}
|
|
|
|
/* 2KB buffer area */
|
|
if (address >= 0x03f800)
|
|
{
|
|
return READ_WORD(megasd_hw.buffer, address & 0x7fe);
|
|
}
|
|
}
|
|
|
|
/* default cartridge area */
|
|
return *(uint16 *)(m68k.memory_map[0x03].base + (address & 0xfffe));
|
|
}
|
|
|
|
/*
|
|
PCM sound chip interface
|
|
*/
|
|
static void megasd_pcm_write_byte(unsigned int address, unsigned int data)
|
|
{
|
|
/* /LDS only */
|
|
if (address & 1)
|
|
{
|
|
pcm_write((address >> 1) & 0x1fff, data, (m68k.cycles * SCYCLES_PER_LINE) / MCYCLES_PER_LINE);
|
|
return;
|
|
}
|
|
|
|
m68k_unused_8_w(address, data);
|
|
return;
|
|
}
|
|
|
|
static void megasd_pcm_write_word(unsigned int address, unsigned int data)
|
|
{
|
|
/* /LDS only */
|
|
pcm_write((address >> 1) & 0x1fff, data & 0xff, (m68k.cycles * SCYCLES_PER_LINE) / MCYCLES_PER_LINE);
|
|
}
|
|
|
|
static unsigned int megasd_pcm_read_byte(unsigned int address)
|
|
{
|
|
/* /LDS only */
|
|
if (address & 1)
|
|
{
|
|
return pcm_read((address >> 1) & 0x1fff, (m68k.cycles * SCYCLES_PER_LINE) / MCYCLES_PER_LINE);
|
|
}
|
|
|
|
return 0x00;
|
|
}
|
|
|
|
static unsigned int megasd_pcm_read_word(unsigned int address)
|
|
{
|
|
/* /LDS only */
|
|
return pcm_read((address >> 1) & 0x1fff, (m68k.cycles * SCYCLES_PER_LINE) / MCYCLES_PER_LINE);
|
|
}
|