HD Packs: Allow replacing game audio
This commit is contained in:
parent
b5ab77d547
commit
a562c71094
21 changed files with 6046 additions and 16 deletions
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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
109
Core/HdAudioDevice.cpp
Normal 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
28
Core/HdAudioDevice.h
Normal 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;
|
||||
};
|
|
@ -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,
|
||||
};
|
|
@ -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, '&');
|
||||
|
|
|
@ -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
110
Core/OggMixer.cpp
Normal 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
34
Core/OggMixer.h
Normal 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
102
Core/OggReader.cpp
Normal 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
39
Core/OggReader.h
Normal 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);
|
||||
};
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
5119
Utilities/stb_vorbis.cpp
Normal file
File diff suppressed because it is too large
Load diff
333
Utilities/stb_vorbis.h
Normal file
333
Utilities/stb_vorbis.h
Normal 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
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
Loading…
Add table
Reference in a new issue