HD Packs: Allow replacing game audio

This commit is contained in:
Souryo 2017-08-19 16:46:57 -04:00
parent b5ab77d547
commit a562c71094
21 changed files with 6046 additions and 16 deletions

View file

@ -29,6 +29,7 @@
#include "RewindManager.h"
#include "SaveStateManager.h"
#include "HdPackBuilder.h"
#include "HdAudioDevice.h"
shared_ptr<Console> Console::Instance(new Console());
@ -114,6 +115,10 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
_memoryManager->RegisterIODevice(_apu.get());
_memoryManager->RegisterIODevice(_controlManager.get());
_memoryManager->RegisterIODevice(_mapper.get());
if(_hdData && (!_hdData->BgmFilesById.empty() || !_hdData->SfxFilesById.empty())) {
_hdAudioDevice.reset(new HdAudioDevice(_hdData.get()));
_memoryManager->RegisterIODevice(_hdAudioDevice.get());
}
_model = NesModel::Auto;
UpdateNesModel(false);
@ -630,6 +635,7 @@ void Console::LoadHdPack(VirtualFile &romFile, VirtualFile &patchFile)
_hdData.reset(new HdPackData());
if(!HdPackLoader::LoadHdNesPack(romFile, *_hdData.get())) {
_hdData.reset();
_hdAudioDevice.reset();
} else {
auto result = _hdData->PatchesByHash.find(romFile.GetSha1Hash());
if(result != _hdData->PatchesByHash.end()) {

View file

@ -16,6 +16,7 @@ class MemoryManager;
class ControlManager;
class AutoSaveManager;
class HdPackBuilder;
class HdAudioDevice;
struct HdPackData;
enum class NesModel;
enum class ScaleFilterType;
@ -42,6 +43,7 @@ class Console
shared_ptr<HdPackBuilder> _hdPackBuilder;
unique_ptr<HdPackData> _hdData;
unique_ptr<HdAudioDevice> _hdAudioDevice;
NesModel _model;

View file

@ -413,11 +413,14 @@
<ClInclude Include="AutomaticRomTest.h" />
<ClInclude Include="BaseRenderer.h" />
<ClInclude Include="FceuxMovie.h" />
<ClInclude Include="HdAudioDevice.h" />
<ClInclude Include="HdBuilderPpu.h" />
<ClInclude Include="HdData.h" />
<ClInclude Include="HdPackBuilder.h" />
<ClInclude Include="HdPackLoader.h" />
<ClInclude Include="Mapper174.h" />
<ClInclude Include="OggMixer.h" />
<ClInclude Include="OggReader.h" />
<ClInclude Include="Rambo1_158.h" />
<ClInclude Include="RecordedRomTest.h" />
<ClInclude Include="AutoSaveManager.h" />
@ -778,9 +781,12 @@
<ClCompile Include="AutomaticRomTest.cpp" />
<ClCompile Include="BaseRenderer.cpp" />
<ClCompile Include="FceuxMovie.cpp" />
<ClCompile Include="HdAudioDevice.cpp" />
<ClCompile Include="HdNesPack.cpp" />
<ClCompile Include="HdPackBuilder.cpp" />
<ClCompile Include="HdPackLoader.cpp" />
<ClCompile Include="OggMixer.cpp" />
<ClCompile Include="OggReader.cpp" />
<ClCompile Include="RecordedRomTest.cpp" />
<ClCompile Include="AutoSaveManager.cpp" />
<ClCompile Include="AviRecorder.cpp" />

View file

@ -25,9 +25,6 @@
<Filter Include="Nes\Interfaces">
<UniqueIdentifier>{ca661408-b52a-4378-aef4-80fda1d64cd6}</UniqueIdentifier>
</Filter>
<Filter Include="VideoDecoder\HD">
<UniqueIdentifier>{a6994cb5-f9d2-416c-84ab-c1abe4975eb1}</UniqueIdentifier>
</Filter>
<Filter Include="Nes\RomLoader">
<UniqueIdentifier>{cfdbaafc-8e74-4e09-80fd-30f8bc833c88}</UniqueIdentifier>
</Filter>
@ -92,6 +89,9 @@
<Filter Include="Rewinder">
<UniqueIdentifier>{52b03b24-dd62-4daf-bac8-bb60a555d3d2}</UniqueIdentifier>
</Filter>
<Filter Include="HdPacks">
<UniqueIdentifier>{a6994cb5-f9d2-416c-84ab-c1abe4975eb1}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="IAudioDevice.h">
@ -194,10 +194,10 @@
<Filter>Nes\Mappers</Filter>
</ClInclude>
<ClInclude Include="HdNesPack.h">
<Filter>VideoDecoder\HD</Filter>
<Filter>HdPacks</Filter>
</ClInclude>
<ClInclude Include="HdPpu.h">
<Filter>VideoDecoder\HD</Filter>
<Filter>HdPacks</Filter>
</ClInclude>
<ClInclude Include="CodeDataLogger.h">
<Filter>Debugger</Filter>
@ -275,7 +275,7 @@
<Filter>Nes</Filter>
</ClInclude>
<ClInclude Include="HdVideoFilter.h">
<Filter>VideoDecoder\HD</Filter>
<Filter>HdPacks</Filter>
</ClInclude>
<ClInclude Include="ExpressionEvaluator.h">
<Filter>Debugger</Filter>
@ -1181,16 +1181,25 @@
<Filter>VideoDecoder</Filter>
</ClInclude>
<ClInclude Include="HdPackBuilder.h">
<Filter>VideoDecoder\HD</Filter>
<Filter>HdPacks</Filter>
</ClInclude>
<ClInclude Include="HdBuilderPpu.h">
<Filter>VideoDecoder\HD</Filter>
<Filter>HdPacks</Filter>
</ClInclude>
<ClInclude Include="HdData.h">
<Filter>VideoDecoder\HD</Filter>
<Filter>HdPacks</Filter>
</ClInclude>
<ClInclude Include="HdPackLoader.h">
<Filter>VideoDecoder\HD</Filter>
<Filter>HdPacks</Filter>
</ClInclude>
<ClInclude Include="OggReader.h">
<Filter>HdPacks</Filter>
</ClInclude>
<ClInclude Include="HdAudioDevice.h">
<Filter>HdPacks</Filter>
</ClInclude>
<ClInclude Include="OggMixer.h">
<Filter>HdPacks</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
@ -1276,7 +1285,7 @@
<Filter>Nes\APU</Filter>
</ClCompile>
<ClCompile Include="HdVideoFilter.cpp">
<Filter>VideoDecoder\HD</Filter>
<Filter>HdPacks</Filter>
</ClCompile>
<ClCompile Include="ExpressionEvaluator.cpp">
<Filter>Debugger</Filter>
@ -1417,13 +1426,22 @@
<Filter>VideoDecoder</Filter>
</ClCompile>
<ClCompile Include="HdPackBuilder.cpp">
<Filter>VideoDecoder\HD</Filter>
<Filter>HdPacks</Filter>
</ClCompile>
<ClCompile Include="HdPackLoader.cpp">
<Filter>VideoDecoder\HD</Filter>
<Filter>HdPacks</Filter>
</ClCompile>
<ClCompile Include="HdNesPack.cpp">
<Filter>VideoDecoder\HD</Filter>
<Filter>HdPacks</Filter>
</ClCompile>
<ClCompile Include="OggMixer.cpp">
<Filter>HdPacks</Filter>
</ClCompile>
<ClCompile Include="OggReader.cpp">
<Filter>Misc</Filter>
</ClCompile>
<ClCompile Include="HdAudioDevice.cpp">
<Filter>Misc</Filter>
</ClCompile>
</ItemGroup>
</Project>

109
Core/HdAudioDevice.cpp Normal file
View file

@ -0,0 +1,109 @@
#include "stdafx.h"
#include "HdAudioDevice.h"
#include "HdData.h"
HdAudioDevice::HdAudioDevice(HdPackData * hdData)
{
_hdData = hdData;
_album = 0;
_flags = 0;
_trackError = false;
_oggMixer = SoundMixer::GetOggMixer();
}
bool HdAudioDevice::PlayBgmTrack(uint8_t track)
{
auto result = _hdData->BgmFilesById.find(_album * 256 + track);
if(result != _hdData->BgmFilesById.end()) {
return !_oggMixer->Play(result->second, false);
} else {
MessageManager::Log("[HDPack] Invalid album+track combination: " + std::to_string(_album) + ":" + std::to_string(track));
return false;
}
}
bool HdAudioDevice::PlaySfx(uint8_t sfxNumber)
{
auto result = _hdData->SfxFilesById.find(_album * 256 + sfxNumber);
if(result != _hdData->SfxFilesById.end()) {
return !_oggMixer->Play(result->second, true);
} else {
MessageManager::Log("[HDPack] Invalid album+sfx number combination: " + std::to_string(_album) + ":" + std::to_string(sfxNumber));
return false;
}
}
void HdAudioDevice::ProcessControlFlags(uint8_t flags)
{
_oggMixer->SetPausedFlag((flags & 0x01) == 0x01);
if(flags & 0x02) {
_oggMixer->StopBgm();
}
if(flags & 0x04) {
_oggMixer->StopSfx();
}
}
void HdAudioDevice::GetMemoryRanges(MemoryRanges & ranges)
{
uint16_t baseRegisterAddr = (_hdData->OptionFlags & (int)HdPackOptions::AlternateRegisterRange) ? 0x5000 : 0x4000;
ranges.SetAllowOverride();
ranges.AddHandler(MemoryOperation::Any, baseRegisterAddr + 0xFF9, baseRegisterAddr + 0xFFF);
}
void HdAudioDevice::WriteRAM(uint16_t addr, uint8_t value)
{
switch(addr & 0xFFF) {
//Playback Options
//Bit 0: Loop BGM
//Bit 1-7: Unused, reserved - must be 0
case 0xFF9: _oggMixer->SetPlaybackOptions(value); break;
//Playback Control
//Bit 0: Toggle Pause/Resume (only affects BGM)
//Bit 1: Stop BGM
//Bit 2: Stop all SFX
//Bit 3-7: Unused, reserved - must be 0
case 0xFFA: ProcessControlFlags(value); break;
//BGM Volume: 0 = mute, 255 = max
//Also has an immediate effect on currently playing BGM
case 0xFFB: _oggMixer->SetBgmVolume(value); break;
//SFX Volume: 0 = mute, 255 = max
//Also has an immediate effect on all currently playing SFX
case 0xFFC: _oggMixer->SetSfxVolume(value); break;
//Album number: 0-255 (Allows for up to 64k BGM and SFX tracks)
//No immediate effect - only affects subsequent $4FFE/$4FFF writes
case 0xFFD: _album = value; break;
//Play BGM track (0-255 = track number)
//Stop the current BGM and starts a new track
case 0xFFE: _trackError = PlayBgmTrack(value); break;
//Play sound effect (0-255 = sfx number)
//Plays a new sound effect (no limit to the number of simultaneous sound effects)
case 0xFFF: _trackError = PlaySfx(value); break;
}
}
uint8_t HdAudioDevice::ReadRAM(uint16_t addr)
{
switch(addr & 0xFFF) {
case 0xFFA:
//Status
return (
(_oggMixer->IsBgmPlaying() ? 1 : 0) |
(_oggMixer->IsSfxPlaying() ? 2 : 0) |
(_trackError ? 4 : 0)
);
case 0xFFC: return 'N'; //NES
case 0xFFD: return 'E'; //Enhanced
case 0xFFE: return 'A'; //Audio
case 0xFFF: return 1; //Revision
}
return 0;
}

28
Core/HdAudioDevice.h Normal file
View file

@ -0,0 +1,28 @@
#pragma once
#include "stdafx.h"
#include "IMemoryHandler.h"
#include "SoundMixer.h"
#include "OggMixer.h"
struct HdPackData;
class HdAudioDevice : public IMemoryHandler
{
private:
HdPackData *_hdData;
uint8_t _album;
uint8_t _flags;
bool _trackError;
OggMixer* _oggMixer;
bool PlayBgmTrack(uint8_t track);
bool PlaySfx(uint8_t sfxNumber);
void ProcessControlFlags(uint8_t flags);
public:
HdAudioDevice(HdPackData *hdData);
void GetMemoryRanges(MemoryRanges &ranges) override;
void WriteRAM(uint16_t addr, uint8_t value) override;
uint8_t ReadRAM(uint16_t addr) override;
};

View file

@ -396,6 +396,8 @@ struct HdPackData
vector<unique_ptr<HdPackCondition>> Conditions;
std::unordered_map<HdTileKey, vector<HdPackTileInfo*>> TileByKey;
std::unordered_map<string, string> PatchesByHash;
std::unordered_map<int, string> BgmFilesById;
std::unordered_map<int, string> SfxFilesById;
vector<uint32_t> Palette;
bool HasOverscanConfig = false;
@ -416,4 +418,5 @@ enum class HdPackOptions
{
None = 0,
NoSpriteLimit = 1,
AlternateRegisterRange = 2,
};

View file

@ -79,6 +79,20 @@ bool HdPackLoader::LoadHdNesPack(VirtualFile &romFile, HdPackData &outData)
return false;
}
bool HdPackLoader::CheckFile(string filename)
{
if(_loadFromZip) {
return _reader.CheckFile(filename);
} else {
ifstream file(FolderUtilities::CombinePath(_hdPackFolder, filename), ios::in | ios::binary);
if(file.good()) {
return true;
}
}
return false;
}
bool HdPackLoader::LoadFile(string filename, vector<uint8_t> &fileData)
{
fileData.clear();
@ -153,6 +167,12 @@ bool HdPackLoader::LoadPack()
} else if(lineContent.substr(0, 9) == "<options>") {
tokens = StringUtilities::Split(lineContent.substr(9), ',');
ProcessOptionTag(tokens);
} else if(lineContent.substr(0, 5) == "<bgm>") {
tokens = StringUtilities::Split(lineContent.substr(5), ',');
ProcessBgmTag(tokens);
} else if(lineContent.substr(0, 5) == "<sfx>") {
tokens = StringUtilities::Split(lineContent.substr(5), ',');
ProcessSfxTag(tokens);
}
}
@ -312,6 +332,8 @@ void HdPackLoader::ProcessOptionTag(vector<string> &tokens)
for(string token : tokens) {
if(token == "disableSpriteLimit") {
_data->OptionFlags |= (int)HdPackOptions::NoSpriteLimit;
} else if(token == "alternateRegisterRange") {
_data->OptionFlags |= (int)HdPackOptions::AlternateRegisterRange;
}
}
}
@ -402,6 +424,55 @@ void HdPackLoader::ProcessBackgroundTag(vector<string> &tokens, vector<HdPackCon
}
}
int HdPackLoader::ProcessSoundTrack(string albumString, string trackString, string filename)
{
int album = std::stoi(albumString);
if(album < 0 || album > 255) {
MessageManager::Log("[HDPack] Invalid album value: " + albumString);
return -1;
}
int track = std::stoi(trackString);
if(track < 0 || track > 255) {
MessageManager::Log("[HDPack] Invalid track value: " + trackString);
return -1;
}
if(!CheckFile(filename)) {
MessageManager::Log("[HDPack] OGG file not found: " + filename);
return -1;
}
return album * 256 + track;
}
void HdPackLoader::ProcessBgmTag(vector<string> &tokens)
{
int trackId = ProcessSoundTrack(tokens[0], tokens[1], tokens[2]);
if(trackId >= 0) {
if(_loadFromZip) {
VirtualFile file(_hdPackFolder, tokens[2]);
_data->BgmFilesById[trackId] = file;
} else {
_data->BgmFilesById[trackId] = FolderUtilities::CombinePath(_hdPackFolder, tokens[2]);
}
}
}
void HdPackLoader::ProcessSfxTag(vector<string> &tokens)
{
int trackId = ProcessSoundTrack(tokens[0], tokens[1], tokens[2]);
if(trackId >= 0) {
if(_loadFromZip) {
VirtualFile file(_hdPackFolder, tokens[2]);
_data->SfxFilesById[trackId] = file;
} else {
_data->SfxFilesById[trackId] = FolderUtilities::CombinePath(_hdPackFolder, tokens[2]);
}
}
}
vector<HdPackCondition*> HdPackLoader::ParseConditionString(string conditionString, vector<unique_ptr<HdPackCondition>> &conditions)
{
vector<string> conditionNames = StringUtilities::Split(conditionString, '&');

View file

@ -22,6 +22,7 @@ private:
bool InitializeLoader(VirtualFile &romPath, HdPackData *data);
bool LoadFile(string filename, vector<uint8_t> &fileData);
bool CheckFile(string filename);
bool LoadPack();
void InitializeHdPack();
@ -29,6 +30,7 @@ private:
void InitializeGlobalConditions();
//Video
bool ProcessImgTag(string src);
void ProcessPatchTag(vector<string> &tokens);
void ProcessOverscanTag(vector<string> &tokens);
@ -37,5 +39,10 @@ private:
void ProcessBackgroundTag(vector<string> &tokens, vector<HdPackCondition*> conditions);
void ProcessOptionTag(vector<string>& tokens);
//Audio
int ProcessSoundTrack(string albumString, string trackString, string filename);
void ProcessBgmTag(vector<string> &tokens);
void ProcessSfxTag(vector<string> &tokens);
vector<HdPackCondition*> ParseConditionString(string conditionString, vector<unique_ptr<HdPackCondition>> &conditions);
};

110
Core/OggMixer.cpp Normal file
View file

@ -0,0 +1,110 @@
#include "stdafx.h"
#include "OggReader.h"
#include "OggMixer.h"
enum class OggPlaybackOptions
{
None = 0x00,
Loop = 0x01
};
OggMixer::OggMixer()
{
Reset();
}
void OggMixer::Reset()
{
_bgm.reset();
_sfx.clear();
_sfxVolume = 128;
_bgmVolume = 45;
_options = 0;
_sampleRate = EmulationSettings::GetSampleRate();
_paused = false;
}
void OggMixer::SetPlaybackOptions(uint8_t options)
{
_options = options;
bool loop = (options & (int)OggPlaybackOptions::Loop) != 0;
if(_bgm) {
_bgm->SetLoopFlag(loop);
}
}
void OggMixer::SetPausedFlag(bool paused)
{
_paused = paused;
}
void OggMixer::StopBgm()
{
_bgm.reset();
}
void OggMixer::StopSfx()
{
_sfx.clear();
}
void OggMixer::SetBgmVolume(uint8_t volume)
{
_bgmVolume = volume;
}
void OggMixer::SetSfxVolume(uint8_t volume)
{
_sfxVolume = volume;
}
bool OggMixer::IsBgmPlaying()
{
return !_paused && _bgm;
}
bool OggMixer::IsSfxPlaying()
{
return _sfx.size() > 0;
}
void OggMixer::SetSampleRate(int sampleRate)
{
_sampleRate = sampleRate;
if(_bgm) {
_bgm->SetSampleRate(sampleRate);
}
for(shared_ptr<OggReader> &sfx : _sfx) {
sfx->SetSampleRate(sampleRate);
}
}
bool OggMixer::Play(string filename, bool isSfx)
{
shared_ptr<OggReader> reader(new OggReader());
bool loop = !isSfx && (_options & (int)OggPlaybackOptions::Loop) != 0;
if(reader->Init(filename, loop, _sampleRate)) {
if(isSfx) {
_sfx.push_back(reader);
} else {
_bgm = reader;
}
return true;
}
return false;
}
void OggMixer::ApplySamples(int16_t * buffer, size_t sampleCount)
{
if(_bgm && !_paused) {
_bgm->ApplySamples(buffer, sampleCount, _bgmVolume);
if(_bgm->IsPlaybackOver()) {
_bgm.reset();
}
}
for(shared_ptr<OggReader> &sfx : _sfx) {
sfx->ApplySamples(buffer, sampleCount, _sfxVolume);
}
_sfx.erase(std::remove_if(_sfx.begin(), _sfx.end(), [](const shared_ptr<OggReader>& o) { return o->IsPlaybackOver(); }), _sfx.end());
}

34
Core/OggMixer.h Normal file
View file

@ -0,0 +1,34 @@
#pragma once
#include "stdafx.h"
class OggReader;
class OggMixer
{
private:
shared_ptr<OggReader> _bgm;
vector<shared_ptr<OggReader>> _sfx;
uint32_t _sampleRate;
uint8_t _bgmVolume;
uint8_t _sfxVolume;
uint8_t _options;
bool _paused;
public:
OggMixer();
void SetSampleRate(int sampleRate);
void ApplySamples(int16_t* buffer, size_t sampleCount);
void Reset();
bool Play(string filename, bool isSfx);
void SetPlaybackOptions(uint8_t options);
void SetPausedFlag(bool paused);
void StopBgm();
void StopSfx();
void SetBgmVolume(uint8_t volume);
void SetSfxVolume(uint8_t volume);
bool IsBgmPlaying();
bool IsSfxPlaying();
};

102
Core/OggReader.cpp Normal file
View file

@ -0,0 +1,102 @@
#include "stdafx.h"
#include "OggReader.h"
OggReader::OggReader()
{
_done = false;
_blipLeft = blip_new(10000);
_blipRight = blip_new(10000);
_oggBuffer = new int16_t[OggReader::SamplesToRead * 2];
_outputBuffer = new int16_t[2000];
}
OggReader::~OggReader()
{
blip_delete(_blipLeft);
blip_delete(_blipRight);
delete[] _oggBuffer;
delete[] _outputBuffer;
if(_vorbis) {
stb_vorbis_close(_vorbis);
}
}
bool OggReader::Init(string filename, bool loop, int sampleRate)
{
int error;
VirtualFile file = filename;
_fileData = vector<uint8_t>(100000);
if(file.ReadFile(_fileData)) {
_vorbis = stb_vorbis_open_memory(_fileData.data(), (int)_fileData.size(), &error, nullptr);
if(_vorbis) {
_loop = loop;
_oggSampleRate = stb_vorbis_get_info(_vorbis).sample_rate;
blip_set_rates(_blipLeft, _oggSampleRate, sampleRate);
blip_set_rates(_blipRight, _oggSampleRate, sampleRate);
return true;
}
}
return false;
}
bool OggReader::IsPlaybackOver()
{
return _done && blip_samples_avail(_blipLeft) == 0;
}
void OggReader::SetSampleRate(int sampleRate)
{
if(sampleRate != _sampleRate) {
blip_clear(_blipLeft);
blip_clear(_blipRight);
_sampleRate = sampleRate;
blip_set_rates(_blipLeft, _oggSampleRate, _sampleRate);
blip_set_rates(_blipRight, _oggSampleRate, _sampleRate);
}
}
void OggReader::SetLoopFlag(bool loop)
{
_loop = loop;
}
bool OggReader::LoadSamples()
{
int samplesReturned = stb_vorbis_get_samples_short_interleaved(_vorbis, 2, _oggBuffer, OggReader::SamplesToRead * 2);
for(int i = 0; i < samplesReturned; i++) {
blip_add_delta(_blipLeft, i, i == 0 ? 0 : (_oggBuffer[i * 2] - _oggBuffer[i * 2 - 2]));
blip_add_delta(_blipRight, i, i == 0 ? 0 : (_oggBuffer[i * 2 + 1] - _oggBuffer[i * 2 - 1]));
}
blip_end_frame(_blipLeft, samplesReturned);
blip_end_frame(_blipRight, samplesReturned);
if(samplesReturned < OggReader::SamplesToRead) {
if(_loop) {
stb_vorbis_seek_start(_vorbis);
LoadSamples();
} else {
_done = true;
}
}
return samplesReturned > 0;
}
void OggReader::ApplySamples(int16_t * buffer, size_t sampleCount, uint8_t volume)
{
while(blip_samples_avail(_blipLeft) < sampleCount) {
if(!LoadSamples()) {
break;
}
}
int samplesRead = blip_read_samples(_blipLeft, _outputBuffer, (int)sampleCount, 1);
blip_read_samples(_blipRight, _outputBuffer + 1, (int)sampleCount, 1);
for(size_t i = 0, len = samplesRead * 2; i < len; i++) {
buffer[i] += (int16_t)(_outputBuffer[i] * (EmulationSettings::GetMasterVolume() * volume / 255 / 10));
}
}

39
Core/OggReader.h Normal file
View file

@ -0,0 +1,39 @@
#pragma once
#include "stdafx.h"
#include "../Utilities/stb_vorbis.h"
#include "../Utilities/blip_buf.h"
#include "../Utilities/VirtualFile.h"
#include "EmulationSettings.h"
class OggReader
{
private:
const int SamplesToRead = 100;
stb_vorbis* _vorbis;
int16_t* _oggBuffer;
int16_t* _outputBuffer;
bool _loop;
bool _done;
blip_t* _blipLeft;
blip_t* _blipRight;
int _sampleRate;
int _oggSampleRate;
vector<uint8_t> _fileData;
bool LoadSamples();
public:
OggReader();
~OggReader();
bool Init(string filename, bool loop, int sampleRate);
bool IsPlaybackOver();
void SetSampleRate(int sampleRate);
void SetLoopFlag(bool loop);
void ApplySamples(int16_t* buffer, size_t sampleCount, uint8_t volume);
};

View file

@ -1,20 +1,25 @@
#include "stdafx.h"
#include "../Utilities/orfanidis_eq.h"
#include "../Utilities/stb_vorbis.h"
#include "SoundMixer.h"
#include "APU.h"
#include "CPU.h"
#include "VideoRenderer.h"
#include "RewindManager.h"
#include "WaveRecorder.h"
#include "OggMixer.h"
IAudioDevice* SoundMixer::AudioDevice = nullptr;
unique_ptr<WaveRecorder> SoundMixer::_waveRecorder;
SimpleLock SoundMixer::_waveRecorderLock;
double SoundMixer::_fadeRatio;
uint32_t SoundMixer::_muteFrameCount;
unique_ptr<OggMixer> SoundMixer::_oggMixer;
SoundMixer::SoundMixer()
{
_eqFrequencyGrid.reset(new orfanidis_eq::freq_grid());
_oggMixer.reset();
_outputBuffer = new int16_t[SoundMixer::MaxSamplesPerFrame];
_blipBufLeft = blip_new(SoundMixer::MaxSamplesPerFrame);
_blipBufRight = blip_new(SoundMixer::MaxSamplesPerFrame);
@ -64,6 +69,9 @@ void SoundMixer::StopAudio(bool clearBuffer)
void SoundMixer::Reset()
{
if(_oggMixer) {
_oggMixer->Reset();
}
_fadeRatio = 1.0;
_muteFrameCount = 0;
@ -102,6 +110,10 @@ void SoundMixer::PlayAudioBuffer(uint32_t time)
}
}
if(_oggMixer) {
_oggMixer->ApplySamples(_outputBuffer, sampleCount);
}
//Apply low pass filter/volume reduction when in background (based on options)
if(!VideoRenderer::GetInstance()->IsRecording() && !_waveRecorder && !EmulationSettings::CheckFlag(EmulationFlags::NsfPlayerEnabled) && EmulationSettings::CheckFlag(EmulationFlags::InBackground)) {
if(EmulationSettings::CheckFlag(EmulationFlags::MuteSoundInBackground)) {
@ -173,6 +185,9 @@ void SoundMixer::UpdateRates(bool forceUpdate)
_clockRate = newRate;
blip_set_rates(_blipBufLeft, _clockRate, _sampleRate);
blip_set_rates(_blipBufRight, _clockRate, _sampleRate);
if(_oggMixer) {
_oggMixer->SetSampleRate(_sampleRate);
}
}
bool hasPanning = false;
@ -347,4 +362,12 @@ uint32_t SoundMixer::GetMuteFrameCount()
void SoundMixer::ResetMuteFrameCount()
{
_muteFrameCount = 0;
}
OggMixer* SoundMixer::GetOggMixer()
{
if(!_oggMixer) {
_oggMixer.reset(new OggMixer());
}
return _oggMixer.get();
}

View file

@ -10,7 +10,9 @@
#include "StereoDelayFilter.h"
#include "ReverbFilter.h"
#include "CrossFeedFilter.h"
#include "WaveRecorder.h"
class WaveRecorder;
class OggMixer;
namespace orfanidis_eq {
class freq_grid;
@ -28,9 +30,10 @@ private:
static SimpleLock _waveRecorderLock;
static double _fadeRatio;
static uint32_t _muteFrameCount;
static unique_ptr<OggMixer> _oggMixer;
static IAudioDevice* AudioDevice;
static const uint32_t MaxSampleRate = 48000;
static const uint32_t MaxSampleRate = 96000;
static const uint32_t MaxSamplesPerFrame = MaxSampleRate / 60 * 4 * 2; //x4 to allow CPU overclocking up to 10x, x2 for panning stereo
static const uint32_t MaxChannelCount = 11;
@ -96,4 +99,6 @@ public:
static void StopAudio(bool clearBuffer = false);
static void RegisterAudioDevice(IAudioDevice *audioDevice);
static OggMixer* GetOggMixer();
};

View file

@ -48,6 +48,12 @@ vector<string> ArchiveReader::GetFileList(std::initializer_list<string> extensio
return filenames;
}
bool ArchiveReader::CheckFile(string filename)
{
vector<string> files = InternalGetFileList();
return std::find(files.begin(), files.end(), filename) != files.end();
}
bool ArchiveReader::LoadArchive(std::istream &in)
{
in.seekg(0, std::ios::end);

View file

@ -18,6 +18,7 @@ public:
std::stringstream GetStream(string filename);
vector<string> GetFileList(std::initializer_list<string> extensions = {});
bool CheckFile(string filename);
virtual bool ExtractFile(string filename, vector<uint8_t> &output) = 0;

View file

@ -349,6 +349,7 @@
<ClInclude Include="Scale2x\scale3x.h" />
<ClInclude Include="Scale2x\scalebit.h" />
<ClInclude Include="sha1.h" />
<ClInclude Include="stb_vorbis.h" />
<ClInclude Include="StringUtilities.h" />
<ClInclude Include="SZReader.h" />
<ClInclude Include="UPnPPortMapper.h" />
@ -485,6 +486,7 @@
<ClCompile Include="sha1.cpp" />
<ClCompile Include="SimpleLock.cpp" />
<ClCompile Include="Socket.cpp" />
<ClCompile Include="stb_vorbis.cpp" />
<ClCompile Include="stdafx.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>

View file

@ -161,6 +161,9 @@
<ClInclude Include="VirtualFile.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="stb_vorbis.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">
@ -277,5 +280,8 @@
<ClCompile Include="sha1.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="stb_vorbis.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

5119
Utilities/stb_vorbis.cpp Normal file

File diff suppressed because it is too large Load diff

333
Utilities/stb_vorbis.h Normal file
View file

@ -0,0 +1,333 @@
//////////////////////////////////////////////////////////////////////////////
//
// HEADER BEGINS HERE
//
#ifndef STB_VORBIS_INCLUDE_STB_VORBIS_H
#define STB_VORBIS_INCLUDE_STB_VORBIS_H
#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO)
#define STB_VORBIS_NO_STDIO 1
#endif
#ifndef STB_VORBIS_NO_STDIO
#include <stdio.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
/////////// THREAD SAFETY
// Individual stb_vorbis* handles are not thread-safe; you cannot decode from
// them from multiple threads at the same time. However, you can have multiple
// stb_vorbis* handles and decode from them independently in multiple thrads.
/////////// MEMORY ALLOCATION
// normally stb_vorbis uses malloc() to allocate memory at startup,
// and alloca() to allocate temporary memory during a frame on the
// stack. (Memory consumption will depend on the amount of setup
// data in the file and how you set the compile flags for speed
// vs. size. In my test files the maximal-size usage is ~150KB.)
//
// You can modify the wrapper functions in the source (setup_malloc,
// setup_temp_malloc, temp_malloc) to change this behavior, or you
// can use a simpler allocation model: you pass in a buffer from
// which stb_vorbis will allocate _all_ its memory (including the
// temp memory). "open" may fail with a VORBIS_outofmem if you
// do not pass in enough data; there is no way to determine how
// much you do need except to succeed (at which point you can
// query get_info to find the exact amount required. yes I know
// this is lame).
//
// If you pass in a non-NULL buffer of the type below, allocation
// will occur from it as described above. Otherwise just pass NULL
// to use malloc()/alloca()
typedef struct
{
char *alloc_buffer;
int alloc_buffer_length_in_bytes;
} stb_vorbis_alloc;
/////////// FUNCTIONS USEABLE WITH ALL INPUT MODES
typedef struct stb_vorbis stb_vorbis;
typedef struct
{
unsigned int sample_rate;
int channels;
unsigned int setup_memory_required;
unsigned int setup_temp_memory_required;
unsigned int temp_memory_required;
int max_frame_size;
} stb_vorbis_info;
// get general information about the file
extern stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f);
// get the last error detected (clears it, too)
extern int stb_vorbis_get_error(stb_vorbis *f);
// close an ogg vorbis file and free all memory in use
extern void stb_vorbis_close(stb_vorbis *f);
// this function returns the offset (in samples) from the beginning of the
// file that will be returned by the next decode, if it is known, or -1
// otherwise. after a flush_pushdata() call, this may take a while before
// it becomes valid again.
// NOT WORKING YET after a seek with PULLDATA API
extern int stb_vorbis_get_sample_offset(stb_vorbis *f);
// returns the current seek point within the file, or offset from the beginning
// of the memory buffer. In pushdata mode it returns 0.
extern unsigned int stb_vorbis_get_file_offset(stb_vorbis *f);
/////////// PUSHDATA API
#ifndef STB_VORBIS_NO_PUSHDATA_API
// this API allows you to get blocks of data from any source and hand
// them to stb_vorbis. you have to buffer them; stb_vorbis will tell
// you how much it used, and you have to give it the rest next time;
// and stb_vorbis may not have enough data to work with and you will
// need to give it the same data again PLUS more. Note that the Vorbis
// specification does not bound the size of an individual frame.
extern stb_vorbis *stb_vorbis_open_pushdata(
const unsigned char * datablock, int datablock_length_in_bytes,
int *datablock_memory_consumed_in_bytes,
int *error,
const stb_vorbis_alloc *alloc_buffer);
// create a vorbis decoder by passing in the initial data block containing
// the ogg&vorbis headers (you don't need to do parse them, just provide
// the first N bytes of the file--you're told if it's not enough, see below)
// on success, returns an stb_vorbis *, does not set error, returns the amount of
// data parsed/consumed on this call in *datablock_memory_consumed_in_bytes;
// on failure, returns NULL on error and sets *error, does not change *datablock_memory_consumed
// if returns NULL and *error is VORBIS_need_more_data, then the input block was
// incomplete and you need to pass in a larger block from the start of the file
extern int stb_vorbis_decode_frame_pushdata(
stb_vorbis *f,
const unsigned char *datablock, int datablock_length_in_bytes,
int *channels, // place to write number of float * buffers
float ***output, // place to write float ** array of float * buffers
int *samples // place to write number of output samples
);
// decode a frame of audio sample data if possible from the passed-in data block
//
// return value: number of bytes we used from datablock
//
// possible cases:
// 0 bytes used, 0 samples output (need more data)
// N bytes used, 0 samples output (resynching the stream, keep going)
// N bytes used, M samples output (one frame of data)
// note that after opening a file, you will ALWAYS get one N-bytes,0-sample
// frame, because Vorbis always "discards" the first frame.
//
// Note that on resynch, stb_vorbis will rarely consume all of the buffer,
// instead only datablock_length_in_bytes-3 or less. This is because it wants
// to avoid missing parts of a page header if they cross a datablock boundary,
// without writing state-machiney code to record a partial detection.
//
// The number of channels returned are stored in *channels (which can be
// NULL--it is always the same as the number of channels reported by
// get_info). *output will contain an array of float* buffers, one per
// channel. In other words, (*output)[0][0] contains the first sample from
// the first channel, and (*output)[1][0] contains the first sample from
// the second channel.
extern void stb_vorbis_flush_pushdata(stb_vorbis *f);
// inform stb_vorbis that your next datablock will not be contiguous with
// previous ones (e.g. you've seeked in the data); future attempts to decode
// frames will cause stb_vorbis to resynchronize (as noted above), and
// once it sees a valid Ogg page (typically 4-8KB, as large as 64KB), it
// will begin decoding the _next_ frame.
//
// if you want to seek using pushdata, you need to seek in your file, then
// call stb_vorbis_flush_pushdata(), then start calling decoding, then once
// decoding is returning you data, call stb_vorbis_get_sample_offset, and
// if you don't like the result, seek your file again and repeat.
#endif
////////// PULLING INPUT API
#ifndef STB_VORBIS_NO_PULLDATA_API
// This API assumes stb_vorbis is allowed to pull data from a source--
// either a block of memory containing the _entire_ vorbis stream, or a
// FILE * that you or it create, or possibly some other reading mechanism
// if you go modify the source to replace the FILE * case with some kind
// of callback to your code. (But if you don't support seeking, you may
// just want to go ahead and use pushdata.)
#if !defined(STB_VORBIS_NO_STDIO) && !defined(STB_VORBIS_NO_INTEGER_CONVERSION)
extern int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output);
#endif
#if !defined(STB_VORBIS_NO_INTEGER_CONVERSION)
extern int stb_vorbis_decode_memory(const unsigned char *mem, int len, int *channels, int *sample_rate, short **output);
#endif
// decode an entire file and output the data interleaved into a malloc()ed
// buffer stored in *output. The return value is the number of samples
// decoded, or -1 if the file could not be opened or was not an ogg vorbis file.
// When you're done with it, just free() the pointer returned in *output.
extern stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len,
int *error, const stb_vorbis_alloc *alloc_buffer);
// create an ogg vorbis decoder from an ogg vorbis stream in memory (note
// this must be the entire stream!). on failure, returns NULL and sets *error
#ifndef STB_VORBIS_NO_STDIO
extern stb_vorbis * stb_vorbis_open_filename(const char *filename,
int *error, const stb_vorbis_alloc *alloc_buffer);
// create an ogg vorbis decoder from a filename via fopen(). on failure,
// returns NULL and sets *error (possibly to VORBIS_file_open_failure).
extern stb_vorbis * stb_vorbis_open_file(FILE *f, int close_handle_on_close,
int *error, const stb_vorbis_alloc *alloc_buffer);
// create an ogg vorbis decoder from an open FILE *, looking for a stream at
// the _current_ seek point (ftell). on failure, returns NULL and sets *error.
// note that stb_vorbis must "own" this stream; if you seek it in between
// calls to stb_vorbis, it will become confused. Morever, if you attempt to
// perform stb_vorbis_seek_*() operations on this file, it will assume it
// owns the _entire_ rest of the file after the start point. Use the next
// function, stb_vorbis_open_file_section(), to limit it.
extern stb_vorbis * stb_vorbis_open_file_section(FILE *f, int close_handle_on_close,
int *error, const stb_vorbis_alloc *alloc_buffer, unsigned int len);
// create an ogg vorbis decoder from an open FILE *, looking for a stream at
// the _current_ seek point (ftell); the stream will be of length 'len' bytes.
// on failure, returns NULL and sets *error. note that stb_vorbis must "own"
// this stream; if you seek it in between calls to stb_vorbis, it will become
// confused.
#endif
extern int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number);
extern int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number);
// these functions seek in the Vorbis file to (approximately) 'sample_number'.
// after calling seek_frame(), the next call to get_frame_*() will include
// the specified sample. after calling stb_vorbis_seek(), the next call to
// stb_vorbis_get_samples_* will start with the specified sample. If you
// do not need to seek to EXACTLY the target sample when using get_samples_*,
// you can also use seek_frame().
extern int stb_vorbis_seek_start(stb_vorbis *f);
// this function is equivalent to stb_vorbis_seek(f,0)
extern unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f);
extern float stb_vorbis_stream_length_in_seconds(stb_vorbis *f);
// these functions return the total length of the vorbis stream
extern int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output);
// decode the next frame and return the number of samples. the number of
// channels returned are stored in *channels (which can be NULL--it is always
// the same as the number of channels reported by get_info). *output will
// contain an array of float* buffers, one per channel. These outputs will
// be overwritten on the next call to stb_vorbis_get_frame_*.
//
// You generally should not intermix calls to stb_vorbis_get_frame_*()
// and stb_vorbis_get_samples_*(), since the latter calls the former.
#ifndef STB_VORBIS_NO_INTEGER_CONVERSION
extern int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts);
extern int stb_vorbis_get_frame_short(stb_vorbis *f, int num_c, short **buffer, int num_samples);
#endif
// decode the next frame and return the number of *samples* per channel.
// Note that for interleaved data, you pass in the number of shorts (the
// size of your array), but the return value is the number of samples per
// channel, not the total number of samples.
//
// The data is coerced to the number of channels you request according to the
// channel coercion rules (see below). You must pass in the size of your
// buffer(s) so that stb_vorbis will not overwrite the end of the buffer.
// The maximum buffer size needed can be gotten from get_info(); however,
// the Vorbis I specification implies an absolute maximum of 4096 samples
// per channel.
// Channel coercion rules:
// Let M be the number of channels requested, and N the number of channels present,
// and Cn be the nth channel; let stereo L be the sum of all L and center channels,
// and stereo R be the sum of all R and center channels (channel assignment from the
// vorbis spec).
// M N output
// 1 k sum(Ck) for all k
// 2 * stereo L, stereo R
// k l k > l, the first l channels, then 0s
// k l k <= l, the first k channels
// Note that this is not _good_ surround etc. mixing at all! It's just so
// you get something useful.
extern int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats);
extern int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples);
// gets num_samples samples, not necessarily on a frame boundary--this requires
// buffering so you have to supply the buffers. DOES NOT APPLY THE COERCION RULES.
// Returns the number of samples stored per channel; it may be less than requested
// at the end of the file. If there are no more samples in the file, returns 0.
#ifndef STB_VORBIS_NO_INTEGER_CONVERSION
extern int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts);
extern int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int num_samples);
#endif
// gets num_samples samples, not necessarily on a frame boundary--this requires
// buffering so you have to supply the buffers. Applies the coercion rules above
// to produce 'channels' channels. Returns the number of samples stored per channel;
// it may be less than requested at the end of the file. If there are no more
// samples in the file, returns 0.
#endif
//////// ERROR CODES
enum STBVorbisError
{
VORBIS__no_error,
VORBIS_need_more_data = 1, // not a real error
VORBIS_invalid_api_mixing, // can't mix API modes
VORBIS_outofmem, // not enough memory
VORBIS_feature_not_supported, // uses floor 0
VORBIS_too_many_channels, // STB_VORBIS_MAX_CHANNELS is too small
VORBIS_file_open_failure, // fopen() failed
VORBIS_seek_without_length, // can't seek in unknown-length file
VORBIS_unexpected_eof = 10, // file is truncated?
VORBIS_seek_invalid, // seek past EOF
// decoding errors (corrupt/invalid stream) -- you probably
// don't care about the exact details of these
// vorbis errors:
VORBIS_invalid_setup = 20,
VORBIS_invalid_stream,
// ogg errors:
VORBIS_missing_capture_pattern = 30,
VORBIS_invalid_stream_structure_version,
VORBIS_continued_packet_flag_invalid,
VORBIS_incorrect_stream_serial_number,
VORBIS_invalid_first_page,
VORBIS_bad_packet_type,
VORBIS_cant_find_last_page,
VORBIS_seek_failed
};
#ifdef __cplusplus
}
#endif
#endif // STB_VORBIS_INCLUDE_STB_VORBIS_H
//
// HEADER ENDS HERE
//
//////////////////////////////////////////////////////////////////////////////