mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
CD audio playback, ADPCM playback
This commit is contained in:
parent
509d67c539
commit
23bd5c599f
28 changed files with 686 additions and 225 deletions
|
@ -23,6 +23,7 @@
|
|||
<ClInclude Include="PCE\Debugger\PceEventManager.h" />
|
||||
<ClInclude Include="PCE\Input\PceController.h" />
|
||||
<ClInclude Include="PCE\PceAdpcm.h" />
|
||||
<ClInclude Include="PCE\PceCdAudioPlayer.h" />
|
||||
<ClInclude Include="PCE\PceCdRom.h" />
|
||||
<ClInclude Include="PCE\PceConstants.h" />
|
||||
<ClInclude Include="PCE\PcePpu.h" />
|
||||
|
@ -448,6 +449,7 @@
|
|||
<ClCompile Include="PCE\Debugger\PcePpuTools.cpp" />
|
||||
<ClCompile Include="PCE\Debugger\PceTraceLogger.cpp" />
|
||||
<ClCompile Include="PCE\PceAdpcm.cpp" />
|
||||
<ClCompile Include="PCE\PceCdAudioPlayer.cpp" />
|
||||
<ClCompile Include="PCE\PceCdRom.cpp" />
|
||||
<ClCompile Include="PCE\PceConsole.cpp" />
|
||||
<ClCompile Include="PCE\PceControlManager.cpp" />
|
||||
|
|
|
@ -1796,6 +1796,9 @@
|
|||
<ClInclude Include="Shared\CdReader.h">
|
||||
<Filter>Shared</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PCE\PceCdAudioPlayer.h">
|
||||
<Filter>PCE</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Shared\Video\RotateFilter.cpp">
|
||||
|
@ -1864,6 +1867,9 @@
|
|||
<ClCompile Include="Shared\CdReader.cpp">
|
||||
<Filter>Shared</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="PCE\PceCdAudioPlayer.cpp">
|
||||
<Filter>PCE</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="PCE">
|
||||
|
|
|
@ -57,7 +57,7 @@ void OggReader::SetLoopFlag(bool loop)
|
|||
|
||||
void OggReader::ApplySamples(int16_t* buffer, size_t sampleCount, uint8_t volume)
|
||||
{
|
||||
int32_t samplesNeeded = (int32_t)sampleCount - _leftoverSampleCount;
|
||||
int32_t samplesNeeded = (int32_t)sampleCount - _resampler.GetPendingCount();
|
||||
uint32_t samplesRead = 0;
|
||||
if(samplesNeeded > 0) {
|
||||
uint32_t samplesToLoad = samplesNeeded * _oggSampleRate / _sampleRate + 2;
|
||||
|
@ -71,21 +71,13 @@ void OggReader::ApplySamples(int16_t* buffer, size_t sampleCount, uint8_t volume
|
|||
}
|
||||
}
|
||||
_resampler.SetSampleRates(_oggSampleRate, _sampleRate);
|
||||
samplesRead = _resampler.Resample(_oggBuffer, samplesLoaded, _outputBuffer + _leftoverSampleCount * 2);
|
||||
samplesRead = _resampler.Resample<false>(_oggBuffer, samplesLoaded, _outputBuffer, sampleCount);
|
||||
}
|
||||
|
||||
|
||||
uint32_t samplesToProcess = std::min<uint32_t>((uint32_t)sampleCount * 2, (samplesRead + _leftoverSampleCount) * 2);
|
||||
uint32_t samplesToProcess = (uint32_t)samplesRead * 2;
|
||||
for(uint32_t i = 0; i < samplesToProcess; i++) {
|
||||
buffer[i] += (int16_t)((int32_t)_outputBuffer[i] * volume / 255);
|
||||
}
|
||||
|
||||
//Calculate count of extra samples that couldn't be mixed with the rest of the audio and copy them to the beginning of the buffer
|
||||
//These will be mixed on the next call to ApplySamples
|
||||
_leftoverSampleCount = std::max(0, (int32_t)(samplesRead + _leftoverSampleCount) - (int32_t)sampleCount);
|
||||
for(uint32_t i = 0; i < _leftoverSampleCount * 2; i++) {
|
||||
_outputBuffer[i] = _outputBuffer[samplesToProcess + i];
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t OggReader::GetOffset()
|
||||
|
|
|
@ -13,7 +13,6 @@ private:
|
|||
int16_t* _oggBuffer = nullptr;
|
||||
|
||||
HermiteResampler _resampler;
|
||||
uint32_t _leftoverSampleCount = 0;
|
||||
|
||||
bool _loop = false;
|
||||
bool _done = false;
|
||||
|
|
|
@ -195,7 +195,7 @@ void PceDebugger::ProcessWrite(uint32_t addr, uint8_t value, MemoryOperationType
|
|||
{
|
||||
AddressInfo addressInfo = _memoryManager->GetAbsoluteAddress(addr);
|
||||
MemoryOperationInfo operation(addr, value, type, MemoryType::PceMemory);
|
||||
if(addressInfo.Address >= 0 && (addressInfo.Type == MemoryType::PceWorkRam)) {
|
||||
if(addressInfo.Address >= 0 && (addressInfo.Type == MemoryType::PceWorkRam || addressInfo.Type == MemoryType::PceCardRam || addressInfo.Type == MemoryType::PceCdromRam)) {
|
||||
_disassembler->InvalidateCache(addressInfo, CpuType::Pce);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,17 +2,23 @@
|
|||
#include "PCE/PceAdpcm.h"
|
||||
#include "PCE/PceScsiBus.h"
|
||||
#include "PCE/PceCdRom.h"
|
||||
#include "PCE/PceConstants.h"
|
||||
#include "Shared/Emulator.h"
|
||||
#include "Shared/MessageManager.h"
|
||||
#include "Utilities/HexUtilities.h"
|
||||
|
||||
using namespace ScsiSignal;
|
||||
|
||||
PceAdpcm::PceAdpcm(PceCdRom* cdrom, PceScsiBus* scsi)
|
||||
PceAdpcm::PceAdpcm(Emulator* emu, PceCdRom* cdrom, PceScsiBus* scsi)
|
||||
{
|
||||
_state = {};
|
||||
_cdrom = cdrom;
|
||||
_scsi = scsi;
|
||||
|
||||
_ram = new uint8_t[0x10000];
|
||||
//todo random
|
||||
memset(_ram, 0, 0x10000);
|
||||
emu->RegisterMemory(MemoryType::PceAdpcmRam, _ram, 0x10000);
|
||||
}
|
||||
|
||||
PceAdpcm::~PceAdpcm()
|
||||
|
@ -26,100 +32,142 @@ void PceAdpcm::Reset()
|
|||
_state.WriteAddress = 0;
|
||||
_state.AddressPort = 0;
|
||||
_state.Playing = false;
|
||||
_state.EndReached = false;
|
||||
_state.HalfReached = false;
|
||||
SetEndReached(false);
|
||||
SetHalfReached(false);
|
||||
_state.Nibble = false;
|
||||
_currentOutput = 0;
|
||||
_magnitude = 0;
|
||||
//TODO
|
||||
}
|
||||
|
||||
void PceAdpcm::SetHalfReached(bool value)
|
||||
{
|
||||
if(_state.HalfReached != value) {
|
||||
_state.HalfReached = value;
|
||||
|
||||
if(value) {
|
||||
_cdrom->SetIrqSource(PceCdRomIrqSource::Adpcm);
|
||||
} else {
|
||||
_cdrom->ClearIrqSource(PceCdRomIrqSource::Adpcm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PceAdpcm::SetEndReached(bool value)
|
||||
{
|
||||
if(_state.EndReached != value) {
|
||||
_state.EndReached = value;
|
||||
|
||||
if(value) {
|
||||
_cdrom->SetIrqSource(PceCdRomIrqSource::Stop);
|
||||
} else {
|
||||
_cdrom->ClearIrqSource(PceCdRomIrqSource::Stop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PceAdpcm::SetControl(uint8_t value)
|
||||
{
|
||||
if((_state.Control & 0x80) && !(value & 0x80)) {
|
||||
Reset();
|
||||
}
|
||||
|
||||
if((value & 0x02) && !(_state.Control & 0x02)) {
|
||||
_state.WriteAddress = _state.AddressPort - ((value & 0x01) ? 0 : 1);
|
||||
LogDebug("[ADPCM] Update write addr");
|
||||
}
|
||||
|
||||
if(value & 0x08) {
|
||||
_state.ReadAddress = _state.AddressPort - ((value & 0x04) ? 0 : 1);
|
||||
LogDebug("[ADPCM] Update read addr");
|
||||
}
|
||||
|
||||
if(value & 0x10) {
|
||||
_state.AdpcmLength = _state.AddressPort;
|
||||
_state.EndReached = false;
|
||||
SetEndReached(false);
|
||||
LogDebug("[ADPCM] Update length");
|
||||
}
|
||||
|
||||
if((value & 0x20) == 0) {
|
||||
_state.Playing = false;
|
||||
LogDebugIf(_state.Playing, "[ADPCM] Stop");
|
||||
} else if(!_state.Playing) {
|
||||
LogDebug("[ADPCM] Play");
|
||||
_state.Playing = true;
|
||||
//TODO
|
||||
//_state.CurrentSample = 2048;
|
||||
_currentOutput = 2048;
|
||||
}
|
||||
|
||||
if(value & 0x80) {
|
||||
LogDebug("[ADPCM] Reset");
|
||||
Reset();
|
||||
}
|
||||
|
||||
_state.Control = value;
|
||||
}
|
||||
|
||||
void PceAdpcm::ProcessReadOperation()
|
||||
{
|
||||
_state.ReadClockCounter--;
|
||||
if(_state.ReadClockCounter == 0) {
|
||||
_state.ReadBuffer = _ram[_state.ReadAddress];
|
||||
_state.ReadAddress++;
|
||||
|
||||
if(_state.AdpcmLength > 0) {
|
||||
_state.AdpcmLength--;
|
||||
} else {
|
||||
SetEndReached(true);
|
||||
SetHalfReached(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PceAdpcm::ProcessWriteOperation()
|
||||
{
|
||||
_state.WriteClockCounter--;
|
||||
if(_state.WriteClockCounter == 0) {
|
||||
_ram[_state.WriteAddress] = _state.WriteBuffer;
|
||||
_state.WriteAddress++;
|
||||
|
||||
if(_state.AdpcmLength < 0xFFFF) {
|
||||
_state.AdpcmLength++;
|
||||
}
|
||||
|
||||
SetHalfReached(_state.AdpcmLength < 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
void PceAdpcm::ProcessDmaRequest()
|
||||
{
|
||||
if(!_scsi->CheckSignal(Ack) && !_scsi->CheckSignal(Cd) && _scsi->CheckSignal(Io) && _scsi->CheckSignal(Req)) {
|
||||
_state.WriteClockCounter = 24;
|
||||
_state.WriteBuffer = _scsi->GetDataPort();
|
||||
_scsi->SetSignalValue(Ack, false);
|
||||
_scsi->SetSignalValue(Req, false);
|
||||
}
|
||||
|
||||
if(!_scsi->IsDataTransferInProgress()) {
|
||||
_state.DmaControl = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void PceAdpcm::Exec()
|
||||
{
|
||||
//TODO decode sample process
|
||||
|
||||
//every 3 master clocks?
|
||||
if(_state.ReadClockCounter > 0) {
|
||||
_state.ReadClockCounter -= 3;
|
||||
if(_state.ReadClockCounter == 0) {
|
||||
_state.ReadBuffer = _ram[_state.ReadAddress];
|
||||
_state.ReadAddress++;
|
||||
|
||||
if(_state.AdpcmLength > 0) {
|
||||
_state.AdpcmLength--;
|
||||
} else {
|
||||
_state.EndReached = true;
|
||||
_state.HalfReached = true;
|
||||
}
|
||||
//Called every 3 master clocks
|
||||
if(_state.Playing) {
|
||||
_nextSampleCounter += 3;
|
||||
if(_nextSampleCounter >= _clocksPerSample) {
|
||||
PlaySample();
|
||||
_nextSampleCounter -= _clocksPerSample;
|
||||
}
|
||||
}
|
||||
|
||||
if(_state.ReadClockCounter > 0) {
|
||||
ProcessReadOperation();
|
||||
}
|
||||
|
||||
if(_state.WriteClockCounter > 0) {
|
||||
_state.WriteClockCounter -= 3;
|
||||
if(_state.WriteClockCounter == 0) {
|
||||
_ram[_state.WriteAddress] = _state.WriteBuffer;
|
||||
|
||||
if(_state.AdpcmLength < 0xFFFF) {
|
||||
_state.AdpcmLength++;
|
||||
}
|
||||
|
||||
_state.HalfReached = _state.AdpcmLength < 0x8000;
|
||||
}
|
||||
ProcessWriteOperation();
|
||||
}
|
||||
|
||||
bool dmaRequested = (_state.DmaControl & 0x03) != 0;
|
||||
if(dmaRequested) {
|
||||
if(!_scsi->CheckSignal(Ack) && !_scsi->CheckSignal(Cd) && _scsi->CheckSignal(Io) && _scsi->CheckSignal(Req)) {
|
||||
_ram[_state.WriteAddress] = _scsi->GetDataPort();
|
||||
_state.WriteAddress++;
|
||||
_state.AdpcmLength++; //limit?
|
||||
|
||||
_scsi->SetSignalValue(Ack, false);
|
||||
_scsi->SetSignalValue(Req, false);
|
||||
}
|
||||
|
||||
if(!_scsi->IsDataTransferInProgress()) {
|
||||
_state.DmaControl = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(_state.HalfReached) {
|
||||
_cdrom->SetIrqSource(PceCdRomIrqSource::Adpcm);
|
||||
} else {
|
||||
_cdrom->ClearIrqSource(PceCdRomIrqSource::Adpcm);
|
||||
}
|
||||
|
||||
if(_state.EndReached) {
|
||||
_cdrom->SetIrqSource(PceCdRomIrqSource::Stop);
|
||||
} else {
|
||||
_cdrom->ClearIrqSource(PceCdRomIrqSource::Stop);
|
||||
if(dmaRequested && !_state.WriteClockCounter) {
|
||||
ProcessDmaRequest();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,13 +180,23 @@ void PceAdpcm::Write(uint16_t addr, uint8_t value)
|
|||
case 0x09: _state.AddressPort = (_state.AddressPort & 0xFF) | (value << 8); break;
|
||||
|
||||
case 0x0A:
|
||||
_state.WriteClockCounter = 72;
|
||||
_state.DataPort = value;
|
||||
_state.WriteClockCounter = 24;
|
||||
_state.WriteBuffer = value;
|
||||
break;
|
||||
|
||||
case 0x0B: _state.DmaControl = value; break;
|
||||
case 0x0D: _state.Control = value; break;
|
||||
case 0x0E: _state.PlaybackRate = value; break;
|
||||
case 0x0B:
|
||||
_state.DmaControl = value;
|
||||
LogDebugIf((value & 0x03), "[ADPCM] DMA");
|
||||
break;
|
||||
|
||||
case 0x0D: SetControl(value); break;
|
||||
case 0x0E: {
|
||||
_state.PlaybackRate = value & 0x0F;
|
||||
double freq = 32000.0 / (16 - _state.PlaybackRate);
|
||||
_clocksPerSample = PceConstants::MasterClockRate / freq;
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x0F: _state.FadeTimer = value; break;
|
||||
|
||||
default:
|
||||
|
@ -149,23 +207,69 @@ void PceAdpcm::Write(uint16_t addr, uint8_t value)
|
|||
|
||||
uint8_t PceAdpcm::Read(uint16_t addr)
|
||||
{
|
||||
LogDebug("Read ADPCM register: " + HexUtilities::ToHex(addr));
|
||||
|
||||
switch(addr & 0x3FF) {
|
||||
case 0x0A:
|
||||
_state.ReadClockCounter = 72;
|
||||
return _state.DataPort;
|
||||
_state.ReadClockCounter = 24;
|
||||
return _state.ReadBuffer;
|
||||
|
||||
case 0x0B: return _state.DmaControl;
|
||||
|
||||
case 0x0C:
|
||||
return (
|
||||
(_state.EndReached ? 0x01 : 0) |
|
||||
(_state.Writing ? 0x04 : 0) |
|
||||
(_state.WriteClockCounter ? 0x04 : 0) |
|
||||
(_state.Playing ? 0x08 : 0) |
|
||||
(_state.Reading ? 0x80 : 0)
|
||||
);
|
||||
(_state.ReadClockCounter ? 0x80 : 0)
|
||||
);
|
||||
|
||||
case 0x0D:
|
||||
return _state.Control;
|
||||
|
||||
default:
|
||||
LogDebug("Read unimplemented ADPCM register: " + HexUtilities::ToHex(addr));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void PceAdpcm::PlaySample()
|
||||
{
|
||||
uint8_t data;
|
||||
if(_state.Nibble) {
|
||||
_state.Nibble = false;
|
||||
data = _ram[_state.ReadAddress] & 0x0F;
|
||||
_state.ReadAddress++;
|
||||
_state.AdpcmLength--;
|
||||
} else {
|
||||
_state.Nibble = true;
|
||||
data = (_ram[_state.ReadAddress] >> 4) & 0x0F;
|
||||
}
|
||||
|
||||
uint8_t value = data & 0x07;
|
||||
int8_t sign = data & 0x08 ? -1 : 1;
|
||||
|
||||
int16_t adjustment = _stepSize[(_magnitude << 3) | value] * sign;
|
||||
_currentOutput = std::clamp(_currentOutput + adjustment, 0, 4095);
|
||||
|
||||
_magnitude = std::clamp(_magnitude + _stepFactor[value], 0, 48);
|
||||
|
||||
SetHalfReached(_state.AdpcmLength < 0x8000);
|
||||
if(_state.AdpcmLength == 0) {
|
||||
_state.Playing = false;
|
||||
SetEndReached(true);
|
||||
SetHalfReached(false);
|
||||
}
|
||||
|
||||
int16_t out = (_currentOutput - 2048) * 10;
|
||||
_samplesToPlay.push_back(out);
|
||||
_samplesToPlay.push_back(out);
|
||||
}
|
||||
|
||||
void PceAdpcm::MixAudio(int16_t* out, uint32_t sampleCount, uint32_t sampleRate)
|
||||
{
|
||||
double freq = 32000.0 / (16 - _state.PlaybackRate);
|
||||
_resampler.SetSampleRates(freq, sampleRate);
|
||||
_resampler.Resample<true>(_samplesToPlay.data(), (uint32_t)_samplesToPlay.size() / 2, out, sampleCount);
|
||||
_samplesToPlay.clear();
|
||||
}
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "Shared/Interfaces/IAudioProvider.h"
|
||||
#include "Utilities/Audio/HermiteResampler.h"
|
||||
#include "PCE/PceConstants.h"
|
||||
|
||||
class Emulator;
|
||||
class PceCdRom;
|
||||
class PceScsiBus;
|
||||
|
||||
struct PceAdpcmState
|
||||
{
|
||||
bool Nibble;
|
||||
uint16_t ReadAddress;
|
||||
uint16_t WriteAddress;
|
||||
|
||||
uint16_t AddressPort;
|
||||
uint8_t DataPort;
|
||||
|
||||
uint8_t DmaControl;
|
||||
uint8_t Control;
|
||||
|
@ -22,34 +26,102 @@ struct PceAdpcmState
|
|||
bool HalfReached;
|
||||
|
||||
bool Playing;
|
||||
bool Reading;
|
||||
bool Writing;
|
||||
|
||||
uint8_t ReadBuffer;
|
||||
uint8_t ReadClockCounter;
|
||||
|
||||
uint8_t WriteBuffer;
|
||||
uint8_t WriteClockCounter;
|
||||
|
||||
};
|
||||
|
||||
class PceAdpcm
|
||||
class PceAdpcm : public IAudioProvider
|
||||
{
|
||||
private:
|
||||
PceCdRom* _cdrom;
|
||||
PceScsiBus* _scsi;
|
||||
PceAdpcmState _state;
|
||||
uint8_t* _ram;
|
||||
HermiteResampler _resampler;
|
||||
|
||||
vector<int16_t> _samplesToPlay;
|
||||
int16_t _currentOutput = 0;
|
||||
uint8_t _magnitude = 0;
|
||||
|
||||
double _clocksPerSample = PceConstants::MasterClockRate / 32000.0;
|
||||
double _nextSampleCounter = 0;
|
||||
|
||||
constexpr static int _stepSize[392] =
|
||||
{
|
||||
0x0002, 0x0006, 0x000A, 0x000E, 0x0012, 0x0016, 0x001A, 0x001E,
|
||||
0x0002, 0x0006, 0x000A, 0x000E, 0x0013, 0x0017, 0x001B, 0x001F,
|
||||
0x0002, 0x0006, 0x000B, 0x000F, 0x0015, 0x0019, 0x001E, 0x0022,
|
||||
0x0002, 0x0007, 0x000C, 0x0011, 0x0017, 0x001C, 0x0021, 0x0026,
|
||||
0x0002, 0x0007, 0x000D, 0x0012, 0x0019, 0x001E, 0x0024, 0x0029,
|
||||
0x0003, 0x0009, 0x000F, 0x0015, 0x001C, 0x0022, 0x0028, 0x002E,
|
||||
0x0003, 0x000A, 0x0011, 0x0018, 0x001F, 0x0026, 0x002D, 0x0034,
|
||||
0x0003, 0x000A, 0x0012, 0x0019, 0x0022, 0x0029, 0x0031, 0x0038,
|
||||
0x0004, 0x000C, 0x0015, 0x001D, 0x0026, 0x002E, 0x0037, 0x003F,
|
||||
0x0004, 0x000D, 0x0016, 0x001F, 0x0029, 0x0032, 0x003B, 0x0044,
|
||||
0x0005, 0x000F, 0x0019, 0x0023, 0x002E, 0x0038, 0x0042, 0x004C,
|
||||
0x0005, 0x0010, 0x001B, 0x0026, 0x0032, 0x003D, 0x0048, 0x0053,
|
||||
0x0006, 0x0012, 0x001F, 0x002B, 0x0038, 0x0044, 0x0051, 0x005D,
|
||||
0x0006, 0x0013, 0x0021, 0x002E, 0x003D, 0x004A, 0x0058, 0x0065,
|
||||
0x0007, 0x0016, 0x0025, 0x0034, 0x0043, 0x0052, 0x0061, 0x0070,
|
||||
0x0008, 0x0018, 0x0029, 0x0039, 0x004A, 0x005A, 0x006B, 0x007B,
|
||||
0x0009, 0x001B, 0x002D, 0x003F, 0x0052, 0x0064, 0x0076, 0x0088,
|
||||
0x000A, 0x001E, 0x0032, 0x0046, 0x005A, 0x006E, 0x0082, 0x0096,
|
||||
0x000B, 0x0021, 0x0037, 0x004D, 0x0063, 0x0079, 0x008F, 0x00A5,
|
||||
0x000C, 0x0024, 0x003C, 0x0054, 0x006D, 0x0085, 0x009D, 0x00B5,
|
||||
0x000D, 0x0027, 0x0042, 0x005C, 0x0078, 0x0092, 0x00AD, 0x00C7,
|
||||
0x000E, 0x002B, 0x0049, 0x0066, 0x0084, 0x00A1, 0x00BF, 0x00DC,
|
||||
0x0010, 0x0030, 0x0051, 0x0071, 0x0092, 0x00B2, 0x00D3, 0x00F3,
|
||||
0x0011, 0x0034, 0x0058, 0x007B, 0x00A0, 0x00C3, 0x00E7, 0x010A,
|
||||
0x0013, 0x003A, 0x0061, 0x0088, 0x00B0, 0x00D7, 0x00FE, 0x0125,
|
||||
0x0015, 0x0040, 0x006B, 0x0096, 0x00C2, 0x00ED, 0x0118, 0x0143,
|
||||
0x0017, 0x0046, 0x0076, 0x00A5, 0x00D5, 0x0104, 0x0134, 0x0163,
|
||||
0x001A, 0x004E, 0x0082, 0x00B6, 0x00EB, 0x011F, 0x0153, 0x0187,
|
||||
0x001C, 0x0055, 0x008F, 0x00C8, 0x0102, 0x013B, 0x0175, 0x01AE,
|
||||
0x001F, 0x005E, 0x009D, 0x00DC, 0x011C, 0x015B, 0x019A, 0x01D9,
|
||||
0x0022, 0x0067, 0x00AD, 0x00F2, 0x0139, 0x017E, 0x01C4, 0x0209,
|
||||
0x0026, 0x0072, 0x00BF, 0x010B, 0x0159, 0x01A5, 0x01F2, 0x023E,
|
||||
0x002A, 0x007E, 0x00D2, 0x0126, 0x017B, 0x01CF, 0x0223, 0x0277,
|
||||
0x002E, 0x008A, 0x00E7, 0x0143, 0x01A1, 0x01FD, 0x025A, 0x02B6,
|
||||
0x0033, 0x0099, 0x00FF, 0x0165, 0x01CB, 0x0231, 0x0297, 0x02FD,
|
||||
0x0038, 0x00A8, 0x0118, 0x0188, 0x01F9, 0x0269, 0x02D9, 0x0349,
|
||||
0x003D, 0x00B8, 0x0134, 0x01AF, 0x022B, 0x02A6, 0x0322, 0x039D,
|
||||
0x0044, 0x00CC, 0x0154, 0x01DC, 0x0264, 0x02EC, 0x0374, 0x03FC,
|
||||
0x004A, 0x00DF, 0x0175, 0x020A, 0x02A0, 0x0335, 0x03CB, 0x0460,
|
||||
0x0052, 0x00F6, 0x019B, 0x023F, 0x02E4, 0x0388, 0x042D, 0x04D1,
|
||||
0x005A, 0x010F, 0x01C4, 0x0279, 0x032E, 0x03E3, 0x0498, 0x054D,
|
||||
0x0063, 0x012A, 0x01F1, 0x02B8, 0x037F, 0x0446, 0x050D, 0x05D4,
|
||||
0x006D, 0x0148, 0x0223, 0x02FE, 0x03D9, 0x04B4, 0x058F, 0x066A,
|
||||
0x0078, 0x0168, 0x0259, 0x0349, 0x043B, 0x052B, 0x061C, 0x070C,
|
||||
0x0084, 0x018D, 0x0296, 0x039F, 0x04A8, 0x05B1, 0x06BA, 0x07C3,
|
||||
0x0091, 0x01B4, 0x02D8, 0x03FB, 0x051F, 0x0642, 0x0766, 0x0889,
|
||||
0x00A0, 0x01E0, 0x0321, 0x0461, 0x05A2, 0x06E2, 0x0823, 0x0963,
|
||||
0x00B0, 0x0210, 0x0371, 0x04D1, 0x0633, 0x0793, 0x08F4, 0x0A54,
|
||||
0x00C2, 0x0246, 0x03CA, 0x054E, 0x06D2, 0x0856, 0x09DA, 0x0B5E
|
||||
};
|
||||
|
||||
constexpr static int _stepFactor[8] = { -1, -1, -1, -1, 2, 4, 6, 8 };
|
||||
|
||||
void Reset();
|
||||
void SetHalfReached(bool value);
|
||||
void SetEndReached(bool value);
|
||||
void SetControl(uint8_t value);
|
||||
void ProcessReadOperation();
|
||||
void ProcessWriteOperation();
|
||||
void ProcessDmaRequest();
|
||||
void PlaySample();
|
||||
|
||||
public:
|
||||
PceAdpcm(PceCdRom* cdrom, PceScsiBus* scsi);
|
||||
PceAdpcm(Emulator* emu, PceCdRom* cdrom, PceScsiBus* scsi);
|
||||
~PceAdpcm();
|
||||
|
||||
void Exec();
|
||||
|
||||
void Write(uint16_t addr, uint8_t value);
|
||||
uint8_t Read(uint16_t addr);
|
||||
|
||||
void MixAudio(int16_t* out, uint32_t sampleCount, uint32_t sampleRate) override;
|
||||
};
|
89
Core/PCE/PceCdAudioPlayer.cpp
Normal file
89
Core/PCE/PceCdAudioPlayer.cpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
#include "stdafx.h"
|
||||
#include "PCE/PceCdAudioPlayer.h"
|
||||
#include "PCE/PceCdRom.h"
|
||||
#include "Shared/CdReader.h"
|
||||
|
||||
PceCdAudioPlayer::PceCdAudioPlayer(PceCdRom* cdrom, DiscInfo& disc)
|
||||
{
|
||||
_cdrom = cdrom;
|
||||
_disc = &disc;
|
||||
}
|
||||
|
||||
void PceCdAudioPlayer::Play(uint32_t startSector)
|
||||
{
|
||||
int32_t track = _disc->GetTrack(startSector);
|
||||
if(track >= 0) {
|
||||
_startSector = startSector;
|
||||
_playing = true;
|
||||
_clockCounter = 0;
|
||||
|
||||
_endSector = _disc->GetTrackLastSector(track);
|
||||
_endBehavior = CdPlayEndBehavior::Stop;
|
||||
|
||||
_currentSample = 0;
|
||||
_currentSector = startSector;
|
||||
}
|
||||
}
|
||||
|
||||
void PceCdAudioPlayer::SetEndPosition(uint32_t endSector, CdPlayEndBehavior endBehavior)
|
||||
{
|
||||
_endSector = endSector;
|
||||
_endBehavior = endBehavior;
|
||||
_playing = true;
|
||||
}
|
||||
|
||||
void PceCdAudioPlayer::Stop()
|
||||
{
|
||||
_playing = false;
|
||||
}
|
||||
|
||||
void PceCdAudioPlayer::PlaySample()
|
||||
{
|
||||
if(_playing) {
|
||||
_leftSample = _disc->ReadLeftSample(_currentSector, _currentSample);
|
||||
_rightSample = _disc->ReadRightSample(_currentSector, _currentSample);
|
||||
_samplesToPlay.push_back(_leftSample / 2);
|
||||
_samplesToPlay.push_back(_rightSample / 2);
|
||||
_currentSample++;
|
||||
if(_currentSample == 588) {
|
||||
//588 samples per 2352-byte sector
|
||||
_currentSample = 0;
|
||||
_currentSector++;
|
||||
|
||||
if(_currentSector > _endSector) {
|
||||
switch(_endBehavior) {
|
||||
case CdPlayEndBehavior::Stop: _playing = false; break;
|
||||
case CdPlayEndBehavior::Loop: _currentSector = _startSector; break;
|
||||
|
||||
case CdPlayEndBehavior::Irq:
|
||||
_playing = false;
|
||||
_cdrom->ClearIrqSource(PceCdRomIrqSource::DataTransferReady);
|
||||
_cdrom->SetIrqSource(PceCdRomIrqSource::DataTransferDone);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_leftSample = 0;
|
||||
_rightSample = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void PceCdAudioPlayer::Exec()
|
||||
{
|
||||
_clockCounter += 3;
|
||||
if(_clockCounter > 487) {
|
||||
//Output one sample every 487 master clocks (~41101.1hz)
|
||||
PlaySample();
|
||||
_clockCounter -= 487;
|
||||
}
|
||||
}
|
||||
|
||||
void PceCdAudioPlayer::MixAudio(int16_t* out, uint32_t sampleCount, uint32_t sampleRate)
|
||||
{
|
||||
_resampler.SetSampleRates(44100, sampleRate);
|
||||
_resampler.Resample<true>(_samplesToPlay.data(), (uint32_t)_samplesToPlay.size() / 2, out, sampleCount);
|
||||
_samplesToPlay.clear();
|
||||
}
|
53
Core/PCE/PceCdAudioPlayer.h
Normal file
53
Core/PCE/PceCdAudioPlayer.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "Shared/Interfaces/IAudioProvider.h"
|
||||
#include "Utilities/Audio/HermiteResampler.h"
|
||||
|
||||
class PceCdRom;
|
||||
struct DiscInfo;
|
||||
|
||||
enum class CdPlayEndBehavior
|
||||
{
|
||||
Stop,
|
||||
Loop,
|
||||
Irq
|
||||
};
|
||||
|
||||
class PceCdAudioPlayer : public IAudioProvider
|
||||
{
|
||||
DiscInfo* _disc = nullptr;
|
||||
PceCdRom* _cdrom = nullptr;
|
||||
|
||||
bool _playing = false;
|
||||
|
||||
uint32_t _startSector = 0;
|
||||
uint32_t _endSector = 0;
|
||||
CdPlayEndBehavior _endBehavior = CdPlayEndBehavior::Stop;
|
||||
|
||||
uint32_t _currentSector = 0;
|
||||
uint32_t _currentSample = 0;
|
||||
|
||||
int16_t _leftSample = 0;
|
||||
int16_t _rightSample = 0;
|
||||
|
||||
vector<int16_t> _samplesToPlay;
|
||||
uint32_t _clockCounter = 0;
|
||||
|
||||
HermiteResampler _resampler;
|
||||
|
||||
void PlaySample();
|
||||
|
||||
public:
|
||||
PceCdAudioPlayer(PceCdRom* cdrom, DiscInfo& disc);
|
||||
|
||||
void Play(uint32_t startSector);
|
||||
void SetEndPosition(uint32_t endSector, CdPlayEndBehavior endBehavior);
|
||||
void Stop();
|
||||
|
||||
void Exec();
|
||||
|
||||
int16_t GetLeftSample() { return _leftSample; }
|
||||
int16_t GetRightSample() { return _rightSample; }
|
||||
|
||||
void MixAudio(int16_t* out, uint32_t sampleCount, uint32_t sampleRate) override;
|
||||
};
|
|
@ -2,20 +2,34 @@
|
|||
#include "PCE/PceCdRom.h"
|
||||
#include "PCE/PceConsole.h"
|
||||
#include "PCE/PceMemoryManager.h"
|
||||
#include "PCE/PceCdAudioPlayer.h"
|
||||
#include "Shared/Emulator.h"
|
||||
#include "Shared/MessageManager.h"
|
||||
#include "Shared/Audio/SoundMixer.h"
|
||||
#include "Utilities/HexUtilities.h"
|
||||
|
||||
using namespace ScsiSignal;
|
||||
|
||||
PceCdRom::PceCdRom(PceConsole* console, DiscInfo& disc) : _scsi(console, this, disc), _adpcm(this, &_scsi)
|
||||
PceCdRom::PceCdRom(Emulator* emu, PceConsole* console, DiscInfo& disc) : _disc(disc), _scsi(console, this, _disc), _adpcm(emu, this, &_scsi), _audioPlayer(this, _disc)
|
||||
{
|
||||
_emu = emu;
|
||||
_console = console;
|
||||
|
||||
_emu->GetSoundMixer()->RegisterAudioProvider(&_audioPlayer);
|
||||
_emu->GetSoundMixer()->RegisterAudioProvider(&_adpcm);
|
||||
}
|
||||
|
||||
PceCdRom::~PceCdRom()
|
||||
{
|
||||
_emu->GetSoundMixer()->UnregisterAudioProvider(&_audioPlayer);
|
||||
_emu->GetSoundMixer()->UnregisterAudioProvider(&_adpcm);
|
||||
}
|
||||
|
||||
void PceCdRom::Exec()
|
||||
{
|
||||
_scsi.Exec();
|
||||
_adpcm.Exec();
|
||||
_audioPlayer.Exec();
|
||||
}
|
||||
|
||||
void PceCdRom::SetIrqSource(PceCdRomIrqSource src)
|
||||
|
@ -107,7 +121,7 @@ uint8_t PceCdRom::Read(uint16_t addr)
|
|||
switch(addr & 0x3FF) {
|
||||
case 0x00: return _scsi.GetStatus();
|
||||
case 0x01: return _scsi.GetDataPort();
|
||||
case 0x02: return _state.EnabledIrqs;
|
||||
case 0x02: return _state.EnabledIrqs | (_scsi.CheckSignal(Ack) ? 0x80 : 0);
|
||||
case 0x03:
|
||||
_state.BramLocked = true;
|
||||
_state.ReadRightChannel = !_state.ReadRightChannel;
|
||||
|
@ -117,15 +131,14 @@ uint8_t PceCdRom::Read(uint16_t addr)
|
|||
(_state.ReadRightChannel ? 0 : 0x02)
|
||||
);
|
||||
|
||||
case 0x04:
|
||||
case 0x05:
|
||||
case 0x06:
|
||||
case 0x07:
|
||||
LogDebug("Read unimplemented CDROM register: " + HexUtilities::ToHex(addr));
|
||||
break;
|
||||
case 0x04: return _scsi.CheckSignal(Rst) ? 0x02 : 0;
|
||||
|
||||
case 0x08:
|
||||
{
|
||||
case 0x05: return (uint8_t)(_state.ReadRightChannel ? _audioPlayer.GetRightSample() : _audioPlayer.GetLeftSample());
|
||||
case 0x06: return (uint8_t)((_state.ReadRightChannel ? _audioPlayer.GetRightSample() : _audioPlayer.GetLeftSample()) >> 8);
|
||||
|
||||
case 0x07: return _state.BramLocked ? 0 : 0x80;
|
||||
|
||||
case 0x08: {
|
||||
uint8_t val = _scsi.GetDataPort();
|
||||
if(_scsi.CheckSignal(Req) && _scsi.CheckSignal(Io) && !_scsi.CheckSignal(Cd)) {
|
||||
_scsi.SetSignalValue(Ack, false);
|
||||
|
@ -139,12 +152,10 @@ uint8_t PceCdRom::Read(uint16_t addr)
|
|||
case 0x0C: case 0x0D: case 0x0E: case 0x0F:
|
||||
return _adpcm.Read(addr);
|
||||
|
||||
case 0xC0: return 0x00;
|
||||
case 0xC1: return 0xAA;
|
||||
case 0xC2: return 0x55;
|
||||
case 0xC3: return 0x00;
|
||||
case 0xC5: return 0xAA;
|
||||
case 0xC6: return 0x55;
|
||||
case 0xC7: return 0x03;
|
||||
case 0xC3: return 0x03;
|
||||
|
||||
default:
|
||||
LogDebug("Read unknown CDROM register: " + HexUtilities::ToHex(addr));
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
#include "stdafx.h"
|
||||
#include "PCE/PceScsiBus.h"
|
||||
#include "PCE/PceAdpcm.h"
|
||||
#include "PCE/PceCdAudioPlayer.h"
|
||||
|
||||
class Emulator;
|
||||
class PceConsole;
|
||||
class PceMemoryManager;
|
||||
|
||||
|
@ -26,18 +28,25 @@ struct PceCdRomState
|
|||
class PceCdRom
|
||||
{
|
||||
private:
|
||||
Emulator* _emu = nullptr;
|
||||
PceConsole* _console = nullptr;
|
||||
|
||||
DiscInfo _disc;
|
||||
PceScsiBus _scsi;
|
||||
PceAdpcm _adpcm;
|
||||
PceCdAudioPlayer _audioPlayer;
|
||||
PceCdRomState _state = {};
|
||||
|
||||
void UpdateIrqState();
|
||||
|
||||
public:
|
||||
PceCdRom(PceConsole* console, DiscInfo& disc);
|
||||
PceCdRom(Emulator* emu, PceConsole* console, DiscInfo& disc);
|
||||
~PceCdRom();
|
||||
|
||||
void Exec();
|
||||
|
||||
PceCdAudioPlayer& GetAudioPlayer() { return _audioPlayer; }
|
||||
|
||||
void SetIrqSource(PceCdRomIrqSource src);
|
||||
void ClearIrqSource(PceCdRomIrqSource src);
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ LoadRomResult PceConsole::LoadRom(VirtualFile& romFile)
|
|||
return LoadRomResult::Failure;
|
||||
}
|
||||
|
||||
_cdrom.reset(new PceCdRom(this, disc));
|
||||
_cdrom.reset(new PceCdRom(_emu, this, disc));
|
||||
} else {
|
||||
romFile.ReadFile(romData);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "PCE/PceScsiBus.h"
|
||||
#include "PCE/PceConsole.h"
|
||||
#include "PCE/PceCdRom.h"
|
||||
#include "PCE/PceCdAudioPlayer.h"
|
||||
#include "Shared/CdReader.h"
|
||||
#include "Shared/MessageManager.h"
|
||||
#include "Utilities/HexUtilities.h"
|
||||
|
@ -11,7 +12,7 @@ using namespace ScsiSignal;
|
|||
|
||||
PceScsiBus::PceScsiBus(PceConsole* console, PceCdRom* cdrom, DiscInfo& disc)
|
||||
{
|
||||
_disc = disc;
|
||||
_disc = &disc;
|
||||
_console = console;
|
||||
_cdrom = cdrom;
|
||||
}
|
||||
|
@ -26,7 +27,7 @@ void PceScsiBus::SetPhase(ScsiPhase phase)
|
|||
_phase = phase;
|
||||
ClearSignals(Bsy, Cd, Io, Msg, Req);
|
||||
|
||||
LogDebug("[SCSI] Phase changed: " + HexUtilities::ToHex((int)phase));
|
||||
//LogDebug("[SCSI] Phase changed: " + HexUtilities::ToHex((int)phase));
|
||||
|
||||
switch(_phase) {
|
||||
case ScsiPhase::BusFree: _cdrom->ClearIrqSource(PceCdRomIrqSource::DataTransferDone); break;
|
||||
|
@ -49,7 +50,7 @@ void PceScsiBus::Reset()
|
|||
_dataTransferDone = false;
|
||||
_cmdBuffer.clear();
|
||||
_dataBuffer.clear();
|
||||
|
||||
_cdrom->GetAudioPlayer().Stop();
|
||||
//LogDebug("[SCSI] Reset");
|
||||
}
|
||||
|
||||
|
@ -152,7 +153,7 @@ void PceScsiBus::ProcessCommandPhase()
|
|||
SetStatusMessage(ScsiStatus::Good, 0);
|
||||
} else if(cmdSize <= _cmdBuffer.size()) {
|
||||
//All bytes received - command has been processed
|
||||
LogDebug("[SCSI] Command recv: " + HexUtilities::ToHex(_cmdBuffer, ' '));
|
||||
//LogDebug("[SCSI] Command recv: " + HexUtilities::ToHex(_cmdBuffer, ' '));
|
||||
ExecCommand(cmd);
|
||||
_cmdBuffer.clear();
|
||||
} else {
|
||||
|
@ -172,28 +173,74 @@ void PceScsiBus::CmdRead()
|
|||
_sector = sector;
|
||||
_sectorsToRead = sectorsToRead;
|
||||
_readStartClock = _console->GetMasterClock();
|
||||
LogDebug("[SCSI] Read sector: " + std::to_string(_sector));
|
||||
_cdrom->GetAudioPlayer().Stop();
|
||||
LogDebug("[SCSI] Read sector: " + std::to_string(_sector) + " to " + std::to_string(_sector + _sectorsToRead));
|
||||
}
|
||||
|
||||
uint32_t PceScsiBus::GetAudioLbaPos()
|
||||
{
|
||||
switch(_cmdBuffer[9] & 0xC0) {
|
||||
case 0x00:
|
||||
return (_cmdBuffer[3] << 16) | (_cmdBuffer[4] << 8) | _cmdBuffer[5];
|
||||
|
||||
case 0x40: {
|
||||
DiscPosition pos;
|
||||
pos.Minutes = CdReader::FromBcd(_cmdBuffer[2]);
|
||||
pos.Seconds = CdReader::FromBcd(_cmdBuffer[3]);
|
||||
pos.Frames = CdReader::FromBcd(_cmdBuffer[4]);
|
||||
return pos.ToLba() - 150;
|
||||
}
|
||||
|
||||
case 0x80: {
|
||||
uint8_t track = CdReader::FromBcd(_cmdBuffer[2]);
|
||||
int32_t sector = _disc->GetTrackFirstSector(track - 1);
|
||||
return sector >= 0 ? sector : 0;
|
||||
}
|
||||
}
|
||||
|
||||
LogDebug("[SCSI] CMD: Audio pos - invalid param");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PceScsiBus::CmdAudioStartPos()
|
||||
{
|
||||
//TODO
|
||||
LogDebug("[SCSI] CMD: Audio start pos");
|
||||
|
||||
uint32_t startSector = GetAudioLbaPos();
|
||||
|
||||
PceCdAudioPlayer& player = _cdrom->GetAudioPlayer();
|
||||
if(_cmdBuffer[1] == 0) {
|
||||
player.Play(startSector);
|
||||
player.Stop();
|
||||
} else {
|
||||
player.Play(startSector);
|
||||
}
|
||||
|
||||
SetStatusMessage(ScsiStatus::Good, 0);
|
||||
_cdrom->SetIrqSource(PceCdRomIrqSource::DataTransferDone);
|
||||
}
|
||||
|
||||
void PceScsiBus::CmdAudioEndPos()
|
||||
{
|
||||
//TODO
|
||||
LogDebug("[SCSI] CMD: Audio end pos");
|
||||
|
||||
uint32_t endSector = GetAudioLbaPos();
|
||||
|
||||
PceCdAudioPlayer& player = _cdrom->GetAudioPlayer();
|
||||
switch(_cmdBuffer[1]) {
|
||||
case 0: player.Stop(); break;
|
||||
case 1: player.SetEndPosition(endSector, CdPlayEndBehavior::Loop); break;
|
||||
case 2: player.SetEndPosition(endSector, CdPlayEndBehavior::Irq); break;
|
||||
case 3: player.SetEndPosition(endSector, CdPlayEndBehavior::Stop); break;
|
||||
}
|
||||
|
||||
SetStatusMessage(ScsiStatus::Good, 0);
|
||||
}
|
||||
|
||||
void PceScsiBus::CmdPause()
|
||||
{
|
||||
//TODO
|
||||
LogDebug("[SCSI] CMD: Audio pause");
|
||||
_cdrom->GetAudioPlayer().Stop();
|
||||
SetStatusMessage(ScsiStatus::Good, 0);
|
||||
}
|
||||
|
||||
|
@ -218,18 +265,6 @@ void PceScsiBus::CmdReadSubCodeQ()
|
|||
SetPhase(ScsiPhase::DataIn);
|
||||
}
|
||||
|
||||
uint8_t ToBcd(uint8_t value)
|
||||
{
|
||||
uint8_t div = value / 10;
|
||||
uint8_t rem = value % 10;
|
||||
return (div << 4) | rem;
|
||||
}
|
||||
|
||||
uint8_t FromBcd(uint8_t value)
|
||||
{
|
||||
return ((value >> 4) & 0x0F) * 10 + (value & 0x0F);
|
||||
}
|
||||
|
||||
void PceScsiBus::CmdReadToc()
|
||||
{
|
||||
LogDebug("[SCSI] CMD: Read ToC");
|
||||
|
@ -238,38 +273,38 @@ void PceScsiBus::CmdReadToc()
|
|||
//Number of tracks
|
||||
_dataBuffer.clear();
|
||||
_dataBuffer.push_back(1);
|
||||
_dataBuffer.push_back(ToBcd((uint8_t)_disc.Tracks.size()));
|
||||
_dataBuffer.push_back(CdReader::ToBcd((uint8_t)_disc->Tracks.size()));
|
||||
SetPhase(ScsiPhase::DataIn);
|
||||
break;
|
||||
|
||||
case 1: {
|
||||
//Total disc length
|
||||
_dataBuffer.clear();
|
||||
_dataBuffer.push_back(ToBcd(_disc.EndPosition.Minutes));
|
||||
_dataBuffer.push_back(ToBcd(_disc.EndPosition.Seconds));
|
||||
_dataBuffer.push_back(ToBcd(_disc.EndPosition.Frames));
|
||||
_dataBuffer.push_back(CdReader::ToBcd(_disc->EndPosition.Minutes));
|
||||
_dataBuffer.push_back(CdReader::ToBcd(_disc->EndPosition.Seconds));
|
||||
_dataBuffer.push_back(CdReader::ToBcd(_disc->EndPosition.Frames));
|
||||
SetPhase(ScsiPhase::DataIn);
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: {
|
||||
uint8_t track = FromBcd(_cmdBuffer[2]);
|
||||
uint8_t track = CdReader::FromBcd(_cmdBuffer[2]);
|
||||
if(track == 0) {
|
||||
track = 1;
|
||||
}
|
||||
|
||||
DiscPosition pos;
|
||||
if(track > _disc.Tracks.size()) {
|
||||
pos = _disc.EndPosition;
|
||||
if(track > _disc->Tracks.size()) {
|
||||
pos = _disc->EndPosition;
|
||||
} else {
|
||||
pos = DiscPosition::FromLba(_disc.Tracks[track - 1].StartPosition.ToLba() + 150);
|
||||
pos = DiscPosition::FromLba(_disc->Tracks[track - 1].StartPosition.ToLba() + 150);
|
||||
}
|
||||
|
||||
_dataBuffer.clear();
|
||||
_dataBuffer.push_back(ToBcd(pos.Minutes));
|
||||
_dataBuffer.push_back(ToBcd(pos.Seconds));
|
||||
_dataBuffer.push_back(ToBcd(pos.Frames));
|
||||
if(track > _disc.Tracks.size() || _disc.Tracks[track - 1].Format == TrackFormat::Audio) {
|
||||
_dataBuffer.push_back(CdReader::ToBcd(pos.Minutes));
|
||||
_dataBuffer.push_back(CdReader::ToBcd(pos.Seconds));
|
||||
_dataBuffer.push_back(CdReader::ToBcd(pos.Frames));
|
||||
if(track > _disc->Tracks.size() || _disc->Tracks[track - 1].Format == TrackFormat::Audio) {
|
||||
_dataBuffer.push_back(0);
|
||||
} else {
|
||||
_dataBuffer.push_back(4);
|
||||
|
@ -289,28 +324,8 @@ void PceScsiBus::ProcessDiscRead()
|
|||
{
|
||||
if(_discReading && _dataBuffer.empty() && _console->GetMasterClock() - _readStartClock > 175000) {
|
||||
//read disc data
|
||||
//_dataBuffer.push_back(...);
|
||||
|
||||
uint32_t seekPos = 0;
|
||||
_dataBuffer.clear();
|
||||
for(size_t i = 0; i < _disc.Tracks.size(); i++) {
|
||||
if(_sector >= _disc.Tracks[i].FirstSector && _sector < _disc.Tracks[i].FirstSector + _disc.Tracks[i].SectorCount) {
|
||||
uint32_t byteOffset = _disc.Tracks[i].FileOffset + (_sector - _disc.Tracks[i].FirstSector) * 2352;
|
||||
|
||||
for(int j = 0; j < 2048; j++) {
|
||||
_dataBuffer.push_back(_disc.Files[_disc.Tracks[i].FileIndex].ReadByte(byteOffset + 16 + j));
|
||||
}
|
||||
break;
|
||||
}
|
||||
seekPos += _disc.Tracks[i].SectorCount;
|
||||
}
|
||||
|
||||
if(_dataBuffer.empty()) {
|
||||
//sector out of range, return empty data?
|
||||
for(int j = 0; j < 2048; j++) {
|
||||
_dataBuffer.push_back(0);
|
||||
}
|
||||
}
|
||||
_disc->ReadDataSector(_sector, _dataBuffer);
|
||||
|
||||
_sector = (_sector + 1) & 0x1FFFFF;
|
||||
_sectorsToRead--;
|
||||
|
@ -339,7 +354,7 @@ uint8_t PceScsiBus::GetStatus()
|
|||
|
||||
void PceScsiBus::SetDataPort(uint8_t data)
|
||||
{
|
||||
LogDebug("[SCSI] CPU data port write: " + HexUtilities::ToHex(data));
|
||||
//LogDebug("[SCSI] CPU data port write: " + HexUtilities::ToHex(data));
|
||||
_dataPort = data;
|
||||
}
|
||||
|
||||
|
@ -364,13 +379,17 @@ void PceScsiBus::SetSignalValue(::ScsiSignal::ScsiSignal signal, bool val)
|
|||
|
||||
void PceScsiBus::Exec()
|
||||
{
|
||||
ProcessDiscRead();
|
||||
|
||||
if(!_stateChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(_signals[Rst]) {
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessDiscRead();
|
||||
|
||||
do {
|
||||
_stateChanged = false;
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ enum class ScsiCommand
|
|||
class PceScsiBus
|
||||
{
|
||||
private:
|
||||
DiscInfo _disc;
|
||||
DiscInfo* _disc;
|
||||
PceConsole* _console = nullptr;
|
||||
PceCdRom* _cdrom = nullptr;
|
||||
|
||||
|
@ -108,9 +108,12 @@ private:
|
|||
void ExecCommand(ScsiCommand cmd);
|
||||
void ProcessCommandPhase();
|
||||
void CmdRead();
|
||||
|
||||
uint32_t GetAudioLbaPos();
|
||||
void CmdAudioStartPos();
|
||||
void CmdAudioEndPos();
|
||||
void CmdPause();
|
||||
|
||||
void CmdReadSubCodeQ();
|
||||
void CmdReadToc();
|
||||
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
SuperGameboy::SuperGameboy(SnesConsole* console, Gameboy* gameboy) : BaseCoprocessor(MemoryType::Register)
|
||||
{
|
||||
_mixBuffer = new int16_t[0x10000];
|
||||
|
||||
_console = console;
|
||||
_emu = console->GetEmulator();
|
||||
_memoryManager = console->GetMemoryManager();
|
||||
|
@ -41,8 +39,6 @@ SuperGameboy::SuperGameboy(SnesConsole* console, Gameboy* gameboy) : BaseCoproce
|
|||
|
||||
SuperGameboy::~SuperGameboy()
|
||||
{
|
||||
delete[] _mixBuffer;
|
||||
|
||||
_emu->GetSoundMixer()->UnregisterAudioProvider(this);
|
||||
}
|
||||
|
||||
|
@ -275,24 +271,10 @@ void SuperGameboy::MixAudio(int16_t* out, uint32_t sampleCount, uint32_t sampleR
|
|||
int16_t* gbSamples = nullptr;
|
||||
uint32_t gbSampleCount = 0;
|
||||
_gameboy->GetSoundSamples(gbSamples, gbSampleCount);
|
||||
_resampler.SetSampleRates(GbApu::SampleRate * _effectiveClockRate / _gameboy->GetMasterClockRate(), sampleRate);
|
||||
|
||||
int32_t outCount = (int32_t)_resampler.Resample(gbSamples, gbSampleCount, _mixBuffer + _mixSampleCount) * 2;
|
||||
_mixSampleCount += outCount;
|
||||
|
||||
int32_t copyCount = (int32_t)std::min(_mixSampleCount, sampleCount*2);
|
||||
if(!_spc->IsMuted()) {
|
||||
for(int32_t i = 0; i < copyCount; i++) {
|
||||
out[i] += _mixBuffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
int32_t remainingSamples = (int32_t)_mixSampleCount - copyCount;
|
||||
if(remainingSamples > 0) {
|
||||
memmove(_mixBuffer, _mixBuffer + copyCount, remainingSamples*sizeof(int16_t));
|
||||
_mixSampleCount = remainingSamples;
|
||||
} else {
|
||||
_mixSampleCount = 0;
|
||||
_resampler.SetSampleRates(GbApu::SampleRate * _effectiveClockRate / _gameboy->GetMasterClockRate(), sampleRate);
|
||||
_resampler.Resample<true>(gbSamples, gbSampleCount, out, sampleCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,8 +46,6 @@ private:
|
|||
uint8_t _lcdBuffer[4][1280] = {};
|
||||
|
||||
HermiteResampler _resampler;
|
||||
int16_t* _mixBuffer = nullptr;
|
||||
uint32_t _mixSampleCount = 0;
|
||||
|
||||
uint8_t GetLcdRow();
|
||||
uint8_t GetLcdBufferRow();
|
||||
|
|
|
@ -122,26 +122,19 @@ void PcmReader::ApplySamples(int16_t *buffer, size_t sampleCount, uint8_t volume
|
|||
return;
|
||||
}
|
||||
|
||||
int32_t samplesNeeded = (int32_t)sampleCount - _leftoverSampleCount;
|
||||
int32_t samplesNeeded = (int32_t)sampleCount - _resampler.GetPendingCount();
|
||||
if(samplesNeeded > 0) {
|
||||
uint32_t samplesToLoad = samplesNeeded * PcmReader::PcmSampleRate / _sampleRate + 2;
|
||||
LoadSamples(samplesToLoad);
|
||||
}
|
||||
|
||||
uint32_t samplesRead = _resampler.Resample(_pcmBuffer.data(), (uint32_t)_pcmBuffer.size() / 2, _outputBuffer + _leftoverSampleCount*2);
|
||||
uint32_t samplesRead = _resampler.Resample<false>(_pcmBuffer.data(), (uint32_t)_pcmBuffer.size() / 2, _outputBuffer, sampleCount);
|
||||
_pcmBuffer.clear();
|
||||
|
||||
uint32_t samplesToProcess = std::min<uint32_t>((uint32_t)sampleCount * 2, (samplesRead + _leftoverSampleCount) * 2);
|
||||
uint32_t samplesToProcess = (uint32_t)samplesRead * 2;
|
||||
for(uint32_t i = 0; i < samplesToProcess; i++) {
|
||||
buffer[i] += (int16_t)((int32_t)_outputBuffer[i] * volume / 255);
|
||||
}
|
||||
|
||||
//Calculate count of extra samples that couldn't be mixed with the rest of the audio and copy them to the beginning of the buffer
|
||||
//These will be mixed on the next call to ApplySamples
|
||||
_leftoverSampleCount = std::max(0, (int32_t)(samplesRead + _leftoverSampleCount) - (int32_t)sampleCount);
|
||||
for(uint32_t i = 0; i < _leftoverSampleCount*2; i++) {
|
||||
_outputBuffer[i] = _outputBuffer[samplesToProcess + i];
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t PcmReader::GetOffset()
|
||||
|
|
|
@ -87,6 +87,7 @@ void SoundMixer::PlayAudioBuffer(int16_t* samples, uint32_t sampleCount, uint32_
|
|||
_rightSample = samples[1];
|
||||
|
||||
int16_t *out = _sampleBuffer;
|
||||
memset(_sampleBuffer, 0, 0x10000 * 2);
|
||||
uint32_t count = _resampler->Resample(samples, sampleCount, sourceRate, cfg.SampleRate, out);
|
||||
|
||||
uint32_t targetRate = (uint32_t)(cfg.SampleRate * _resampler->GetRateAdjustment());
|
||||
|
|
|
@ -100,5 +100,5 @@ void SoundResampler::UpdateTargetSampleRate(uint32_t sourceRate, uint32_t sample
|
|||
uint32_t SoundResampler::Resample(int16_t *inSamples, uint32_t sampleCount, uint32_t sourceRate, uint32_t sampleRate, int16_t *outSamples)
|
||||
{
|
||||
UpdateTargetSampleRate(sourceRate, sampleRate);
|
||||
return _resampler.Resample(inSamples, sampleCount, outSamples);
|
||||
return _resampler.Resample<false>(inSamples, sampleCount, outSamples, 0);
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
#include "stdafx.h"
|
||||
#include "CdReader.h"
|
||||
#include "Shared/MessageManager.h"
|
||||
#include "Utilities/StringUtilities.h"
|
||||
#include "Utilities/FolderUtilities.h"
|
||||
|
||||
struct CueIndexEntry
|
||||
{
|
||||
|
@ -20,13 +23,12 @@ struct CueFileEntry
|
|||
vector<CueTrackEntry> Tracks;
|
||||
};
|
||||
|
||||
bool CdReader::LoadCue(string path, DiscInfo& disc)
|
||||
bool CdReader::LoadCue(VirtualFile& cueFile, DiscInfo& disc)
|
||||
{
|
||||
vector<CueFileEntry> files;
|
||||
|
||||
VirtualFile file = path;
|
||||
stringstream ss;
|
||||
file.ReadFile(ss);
|
||||
cueFile.ReadFile(ss);
|
||||
|
||||
string line;
|
||||
while(std::getline(ss, line)) {
|
||||
|
@ -37,8 +39,12 @@ bool CdReader::LoadCue(string path, DiscInfo& disc)
|
|||
size_t end = line.find_last_of('"');
|
||||
if(start != end && start != string::npos && end != string::npos) {
|
||||
string filename = line.substr(start + 1, end - start - 1);
|
||||
string filepath = file.GetFolderPath() + filename;
|
||||
files.push_back({ filepath });
|
||||
|
||||
VirtualFile dataFile = cueFile.GetFolderPath() + filename;
|
||||
if(cueFile.IsArchive()) {
|
||||
dataFile = VirtualFile(cueFile.GetFilePath(), filename);
|
||||
}
|
||||
files.push_back({ dataFile });
|
||||
}
|
||||
} else if(line.substr(0, 5) == string("TRACK")) {
|
||||
if(files.size() == 0) {
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "Shared/MessageManager.h"
|
||||
#include "Utilities/VirtualFile.h"
|
||||
#include "Utilities/StringUtilities.h"
|
||||
#include "Utilities/FolderUtilities.h"
|
||||
#include "Shared/MessageManager.h"
|
||||
|
||||
enum class TrackFormat
|
||||
{
|
||||
|
@ -57,10 +55,88 @@ struct DiscInfo
|
|||
uint32_t DiscSize;
|
||||
uint32_t DiscSectorCount;
|
||||
DiscPosition EndPosition;
|
||||
|
||||
int32_t GetTrack(uint32_t sector)
|
||||
{
|
||||
for(size_t i = 0; i < Tracks.size(); i++) {
|
||||
if(Tracks[i].LastSector > sector) {
|
||||
return (int32_t)i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t GetTrackFirstSector(int32_t track)
|
||||
{
|
||||
if(track < Tracks.size()) {
|
||||
return Tracks[track].FirstSector;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t GetTrackLastSector(int32_t track)
|
||||
{
|
||||
if(track < Tracks.size()) {
|
||||
return Tracks[track].LastSector;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ReadDataSector(uint32_t sector, deque<uint8_t>& outData)
|
||||
{
|
||||
int32_t track = GetTrack(sector);
|
||||
if(track < 0) {
|
||||
LogDebug("Invalid sector/track");
|
||||
for(int j = 0; j < 2048; j++) {
|
||||
outData.push_back(0);
|
||||
}
|
||||
} else {
|
||||
uint32_t fileIndex = Tracks[track].FileIndex;
|
||||
uint32_t byteOffset = Tracks[track].FileOffset + (sector - Tracks[track].FirstSector) * 2352;
|
||||
for(int j = 0; j < 2048; j++) {
|
||||
outData.push_back(Files[fileIndex].ReadByte(byteOffset + 16 + j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int16_t ReadAudioSample(uint32_t sector, uint32_t sample, uint32_t byteOffset)
|
||||
{
|
||||
int32_t track = GetTrack(sector);
|
||||
if(track < 0) {
|
||||
LogDebug("Invalid sector/track");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t fileIndex = Tracks[track].FileIndex;
|
||||
uint32_t startByte = Tracks[track].FileOffset + (sector - Tracks[track].FirstSector) * 2352;
|
||||
return (int16_t)(Files[fileIndex].ReadByte(startByte + sample * 4 + byteOffset) | (Files[fileIndex].ReadByte(startByte + sample * 4 + 1 + byteOffset) << 8));
|
||||
}
|
||||
|
||||
int16_t ReadLeftSample(uint32_t sector, uint32_t sample)
|
||||
{
|
||||
return ReadAudioSample(sector, sample, 0);
|
||||
}
|
||||
|
||||
int16_t ReadRightSample(uint32_t sector, uint32_t sample)
|
||||
{
|
||||
return ReadAudioSample(sector, sample, 2);
|
||||
}
|
||||
};
|
||||
|
||||
class CdReader
|
||||
{
|
||||
public:
|
||||
static bool LoadCue(string path, DiscInfo& disc);
|
||||
static bool LoadCue(VirtualFile& file, DiscInfo& disc);
|
||||
|
||||
static uint8_t ToBcd(uint8_t value)
|
||||
{
|
||||
uint8_t div = value / 10;
|
||||
uint8_t rem = value % 10;
|
||||
return (div << 4) | rem;
|
||||
}
|
||||
|
||||
static uint8_t FromBcd(uint8_t value)
|
||||
{
|
||||
return ((value >> 4) & 0x0F) * 10 + (value & 0x0F);
|
||||
}
|
||||
};
|
|
@ -8,8 +8,10 @@
|
|||
|
||||
#ifdef _DEBUG
|
||||
#define LogDebug(msg) MessageManager::Log(msg);
|
||||
#define LogDebugIf(cond, msg) if(cond) { MessageManager::Log(msg); }
|
||||
#else
|
||||
#define LogDebug(msg)
|
||||
#define LogDebugIf(cond, msg)
|
||||
#endif
|
||||
|
||||
class MessageManager
|
||||
|
|
|
@ -83,6 +83,7 @@ namespace Mesen.Config
|
|||
Snes.ApplyConfig();
|
||||
Nes.ApplyConfig();
|
||||
Gameboy.ApplyConfig();
|
||||
Pce.ApplyConfig();
|
||||
|
||||
ConfigApi.SetDebuggerFlag(DebuggerFlags.BreakOnUninitRead, BreakOnUninitRead);
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@ namespace Mesen.Utilities
|
|||
private static HashSet<string> _romExtensions = new HashSet<string>() {
|
||||
".sfc", ".smc", ".fig", ".swc", ".bs",
|
||||
".gb", ".gbc",
|
||||
".nes", ".unif", ".fds", ".studybox"
|
||||
".nes", ".unif", ".fds", ".studybox",
|
||||
".pce", ".cue"
|
||||
};
|
||||
|
||||
public static bool IsRomFile(string path)
|
||||
|
|
|
@ -5,25 +5,20 @@
|
|||
//Original author: Paul Bourke ("Any source code found here may be freely used provided credits are given to the author.")
|
||||
int16_t HermiteResampler::HermiteInterpolate(double values[4], double mu)
|
||||
{
|
||||
constexpr double tension = 0; //Tension: 1 is high, 0 normal, -1 is low
|
||||
constexpr double bias = 0; //Bias: 0 is even, positive is towards first segment, negative towards the other
|
||||
|
||||
double m0, m1, mu2, mu3;
|
||||
double a0, a1, a2, a3;
|
||||
|
||||
mu2 = mu * mu;
|
||||
mu3 = mu2 * mu;
|
||||
m0 = (values[1] - values[0]) * (1 + bias) * (1 - tension) / 2;
|
||||
m0 += (values[2] - values[1]) * (1 - bias) * (1 - tension) / 2;
|
||||
m1 = (values[2] - values[1]) * (1 + bias) * (1 - tension) / 2;
|
||||
m1 += (values[3] - values[2]) * (1 - bias) * (1 - tension) / 2;
|
||||
m0 = (values[1] - values[0]) / 2 + (values[2] - values[1]) / 2;
|
||||
m1 = (values[2] - values[1]) / 2 + (values[3] - values[2]) / 2;
|
||||
a0 = 2 * mu3 - 3 * mu2 + 1;
|
||||
a1 = mu3 - 2 * mu2 + mu;
|
||||
a2 = mu3 - mu2;
|
||||
a3 = -2 * mu3 + 3 * mu2;
|
||||
|
||||
double output = a0 * values[1] + a1 * m0 + a2 * m1 + a3 * values[2];
|
||||
return (int16_t)std::max(std::min(output, 32767.0), -32768.0);
|
||||
return (int16_t)std::clamp(output, -32768.0, 32767.0);
|
||||
}
|
||||
|
||||
void HermiteResampler::PushSample(double prevValues[4], int16_t sample)
|
||||
|
@ -48,21 +43,49 @@ void HermiteResampler::SetSampleRates(double srcRate, double dstRate)
|
|||
_rateRatio = srcRate / dstRate;
|
||||
}
|
||||
|
||||
uint32_t HermiteResampler::Resample(int16_t* in, uint32_t inSampleCount, int16_t* out)
|
||||
uint32_t HermiteResampler::GetPendingCount()
|
||||
{
|
||||
return (uint32_t)_pendingSamples.size() / 2;
|
||||
}
|
||||
|
||||
template<bool addMode>
|
||||
uint32_t HermiteResampler::Resample(int16_t* in, uint32_t inSampleCount, int16_t* out, size_t maxOutSampleCount)
|
||||
{
|
||||
if(_rateRatio == 1.0) {
|
||||
memcpy(out, in, inSampleCount * 2 * sizeof(int16_t));
|
||||
return inSampleCount;
|
||||
}
|
||||
|
||||
uint32_t outPos = 0;
|
||||
maxOutSampleCount *= 2;
|
||||
|
||||
uint32_t outPos = (uint32_t)_pendingSamples.size();
|
||||
for(uint32_t i = 0; i < outPos; i++) {
|
||||
if(addMode) {
|
||||
out[i] += _pendingSamples[i];
|
||||
} else {
|
||||
out[i] = _pendingSamples[i];
|
||||
}
|
||||
}
|
||||
_pendingSamples.clear();
|
||||
|
||||
for(uint32_t i = 0; i < inSampleCount * 2; i += 2) {
|
||||
while(_fraction <= 1.0) {
|
||||
//Generate interpolated samples until we have enough samples for the current source sample
|
||||
out[outPos] = HermiteInterpolate(_prevLeft, _fraction);
|
||||
out[outPos + 1] = HermiteInterpolate(_prevRight, _fraction);
|
||||
outPos += 2;
|
||||
if(maxOutSampleCount == 0 || outPos <= maxOutSampleCount - 2) {
|
||||
if(addMode) {
|
||||
out[outPos] += HermiteInterpolate(_prevLeft, _fraction);
|
||||
out[outPos + 1] += HermiteInterpolate(_prevRight, _fraction);
|
||||
} else {
|
||||
out[outPos] = HermiteInterpolate(_prevLeft, _fraction);
|
||||
out[outPos + 1] = HermiteInterpolate(_prevRight, _fraction);
|
||||
}
|
||||
|
||||
outPos += 2;
|
||||
} else {
|
||||
_pendingSamples.push_back(HermiteInterpolate(_prevLeft, _fraction));
|
||||
_pendingSamples.push_back(HermiteInterpolate(_prevRight, _fraction));
|
||||
}
|
||||
|
||||
_fraction += _rateRatio;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ private:
|
|||
double _rateRatio = 1.0;
|
||||
double _fraction = 0.0;
|
||||
|
||||
vector<int16_t> _pendingSamples;
|
||||
|
||||
__forceinline int16_t HermiteInterpolate(double values[4], double mu);
|
||||
__forceinline void PushSample(double prevValues[4], int16_t sample);
|
||||
|
||||
|
@ -16,5 +18,11 @@ public:
|
|||
void Reset();
|
||||
|
||||
void SetSampleRates(double srcRate, double dstRate);
|
||||
uint32_t Resample(int16_t* in, uint32_t inSampleCount, int16_t* out);
|
||||
};
|
||||
uint32_t GetPendingCount();
|
||||
|
||||
template<bool addMode>
|
||||
uint32_t Resample(int16_t* in, uint32_t inSampleCount, int16_t* out, size_t maxOutSampleCount);
|
||||
};
|
||||
|
||||
template uint32_t HermiteResampler::Resample<true>(int16_t* in, uint32_t inSampleCount, int16_t* out, size_t maxOutSampleCount);
|
||||
template uint32_t HermiteResampler::Resample<false>(int16_t* in, uint32_t inSampleCount, int16_t* out, size_t maxOutSampleCount);
|
|
@ -14,7 +14,7 @@ const std::initializer_list<string> VirtualFile::RomExtensions = {
|
|||
".nes", ".fds", ".unif", ".unf", ".nsf", ".nsfe", ".studybox",
|
||||
".sfc", ".swc", ".fig", ".smc", ".bs", ".spc",
|
||||
".gb", ".gbc", ".gbs",
|
||||
".pce", "cue"
|
||||
".pce", ".cue"
|
||||
};
|
||||
|
||||
VirtualFile::VirtualFile()
|
||||
|
@ -131,6 +131,11 @@ bool VirtualFile::IsValid()
|
|||
return false;
|
||||
}
|
||||
|
||||
bool VirtualFile::IsArchive()
|
||||
{
|
||||
return !_innerFile.empty();
|
||||
}
|
||||
|
||||
string VirtualFile::GetFilePath()
|
||||
{
|
||||
return _path;
|
||||
|
@ -162,10 +167,15 @@ size_t VirtualFile::GetSize()
|
|||
if(_data.size() > 0) {
|
||||
return _data.size();
|
||||
} else {
|
||||
ifstream input(_path, std::ios::in | std::ios::binary);
|
||||
if(input) {
|
||||
input.seekg(0, std::ios::end);
|
||||
return (uint32_t)input.tellg();
|
||||
if(IsArchive()) {
|
||||
LoadFile();
|
||||
return _data.size();
|
||||
} else {
|
||||
ifstream input(_path, std::ios::in | std::ios::binary);
|
||||
if(input) {
|
||||
input.seekg(0, std::ios::end);
|
||||
return (uint32_t)input.tellg();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ public:
|
|||
operator std::string() const;
|
||||
|
||||
bool IsValid();
|
||||
bool IsArchive();
|
||||
string GetFilePath();
|
||||
string GetFolderPath();
|
||||
string GetFileName();
|
||||
|
|
Loading…
Add table
Reference in a new issue