Skip to content

Commit

Permalink
BGM modding: Add Ogg Vorbis support. [V]
Browse files Browse the repository at this point in the history
Was that a typo in xiph/vorbis@89f651f? 👀
Staying one commit below that for now, who knows how to *actually*
report bugs to a Xiph.org project…

Funded by Splashman.
  • Loading branch information
nmlgc committed Dec 3, 2018
1 parent 1870f55 commit f6f953d
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 2 deletions.
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -98,6 +98,10 @@ All required third-party libraries for the C/C++ code are included as Git submod

* [zlib](http://zlib.net/), required by `thcrap_update` for CRC32 verification. It's required by `libpng` anyway, though.

* `thcrap_bgmmod` currently supports the following codecs via third-party libraries:

* **Ogg Vorbis** via [libogg](https://xiph.org/ogg/), [libvorbis](https://xiph.org/vorbis/) and libvorbisfile

The scripts in the `scripts` directory are written in [Python 3](http://python.org/). Some of them require further third-party libraries not included in this repository:

* [PyCrypto](https://www.dlitz.net/software/pycrypto/) is required by `release_sign.py`.
Expand Down
2 changes: 1 addition & 1 deletion libs/libogg.vcxproj
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug Static|Win32">
Expand Down
2 changes: 1 addition & 1 deletion libs/libvorbis_decode.vcxproj
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug Static|Win32">
Expand Down
4 changes: 4 additions & 0 deletions thcrap.sln
Expand Up @@ -69,6 +69,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libvorbis_decode", "libs\li
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "thcrap_bgmmod", "thcrap_bgmmod\thcrap_bgmmod.vcxproj", "{B55C529E-B1A6-460E-8E3A-F750FC835A73}"
ProjectSection(ProjectDependencies) = postProject
{3A214E06-B95E-4D61-A291-1F8DF2EC10FD} = {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}
{15CBFEFF-7965-41F5-B4E2-21E8795C9159} = {15CBFEFF-7965-41F5-B4E2-21E8795C9159}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
15 changes: 15 additions & 0 deletions thcrap_bgmmod/src/bgmmod.cpp
Expand Up @@ -13,6 +13,13 @@
/// String constants
/// ----------------
const stringref_t LOOP_INFIX = ".loop";
const int LONGEST_CODEC_LEN = [] {
int ret = 0;
for(const auto &codec : CODECS) {
ret = max(ret, codec.ext.len);
}
return ret;
}();
/// ----------------

/// Track class
Expand Down Expand Up @@ -77,6 +84,14 @@ std::unique_ptr<track_pcm_t> pcm_open(
}
/// -----------

/// Codecs
/// ------
std::unique_ptr<pcm_part_t> vorbis_open(HANDLE &&stream);
const codec_t CODECS[1] = {
".ogg", vorbis_open,
};
/// ------

/// Error reporting and debugging
/// -----------------------------
void bgmmod_verrorf(const char *text, va_list va)
Expand Down
2 changes: 2 additions & 0 deletions thcrap_bgmmod/src/bgmmod.hpp
Expand Up @@ -109,6 +109,8 @@ struct codec_t {
stringref_t ext;
pcm_part_open_t &open;
};

extern const codec_t CODECS[1];
/// ------

/// Error reporting and debugging
Expand Down
139 changes: 139 additions & 0 deletions thcrap_bgmmod/src/vorbis.cpp
@@ -0,0 +1,139 @@
/**
* Touhou Community Reliant Automatic Patcher
* BGM modding library for games using uncompressed PCM music
*
* ----
*
* Ogg Vorbis support.
*/

#include <thcrap.h>
#include "bgmmod.hpp"

#define OV_EXCLUDE_STATIC_CALLBACKS
#include <vorbis/vorbisfile.h>

#pragma comment(lib, "libogg" DEBUG_OR_RELEASE)
#pragma comment(lib, "libvorbis" DEBUG_OR_RELEASE)

#define vorbis_errorf(text, ...) \
bgmmod_format_errorf("Ogg Vorbis", text, ##__VA_ARGS__)

/// Callbacks
/// ---------
size_t ov_w32_read(void* dst, size_t elmsize, size_t count, HANDLE file)
{
DWORD byte_ret;
ReadFile(file, dst, elmsize * count, &byte_ret, nullptr);
return byte_ret;
}

int ov_w32_seek(HANDLE file, ogg_int64_t bytes, int org)
{
return SetFilePointer(file, (LONG)bytes, nullptr, org) != 0xFFFFFFFF;
}

int ov_w32_close(HANDLE file)
{
return CloseHandle(file);
}

long ov_w32_tell(HANDLE file)
{
return SetFilePointer(file, 0, 0, FILE_CURRENT);
}

const ov_callbacks OV_CALLBACKS_WIN32 = {
ov_w32_read, ov_w32_seek, ov_w32_close, ov_w32_tell
};
/// ---------

/// libvorbis extensions
/// --------------------
const char* ov_strerror(int ret)
{
// Adapted from doc/libvorbis/return.html.
switch(ret) {
// Despite what the documentation says, this one actually *can* be
// unrecoverable.
case OV_HOLE: return "Missing or corrupt data in the bitstream";
case OV_EREAD: return "Read error while fetching compressed data for decode";
case OV_EFAULT: return "Internal inconsistency in encode or decode state. Continuing is likely not possible.";
case OV_EIMPL: return "Feature not implemented";
case OV_EINVAL: return "Invalid argument was passed to a call";
case OV_ENOTVORBIS: return "The given file was not recognized as Ogg Vorbis data.";
case OV_EBADHEADER: return "The file is apparently an Ogg Vorbis stream, but contains a corrupted or undecipherable header.";
case OV_EVERSION: return "The bitstream format revision of the given file is not supported.";
case OV_EBADLINK:return "The given link exists in the Vorbis data stream, but is not decipherable due to garbage or corruption.";
case OV_ENOSEEK: return "File is not seekable";
default:
return "";
}
}
/// --------------------

struct vorbis_part_t : public pcm_part_t {
OggVorbis_File vf;

size_t part_decode(void *buf, size_t size);
void part_seek_to_sample(size_t sample);

vorbis_part_t(OggVorbis_File &&vf, pcm_format_t pcmf, size_t part_bytes)
: vf(vf), pcm_part_t(pcmf, part_bytes) {
};
virtual ~vorbis_part_t();
};

size_t vorbis_part_t::part_decode(void *buf, size_t size)
{
auto ret = ov_read(&vf, (char *)buf, size, 0, 2, 1, nullptr);
if(ret < 0) {
vorbis_errorf(
"Error %d at sample %lld: %s",
ret, ov_pcm_tell(&vf), ov_strerror(ret)
);
return -1;
}
return ret;
}

void vorbis_part_t::part_seek_to_sample(size_t sample)
{
auto ret = ov_pcm_seek(&vf, sample);
assert(ret == 0);
}

vorbis_part_t::~vorbis_part_t()
{
ov_clear(&vf);
}

std::unique_ptr<pcm_part_t> vorbis_open(HANDLE &&stream)
{
OggVorbis_File vf = { 0 };

auto fail = [&] (int ret) {
vorbis_errorf("%s", ov_strerror(ret));
ov_clear(&vf);
return nullptr;
};

auto ret = ov_open_callbacks(stream, &vf, nullptr, 0, OV_CALLBACKS_WIN32);
if(ret) {
return fail(ret);
}

auto sample_length = ov_pcm_total(&vf, -1);
if(sample_length < 0) {
// The regularly returned error would be OV_EINVAL...
return fail(OV_ENOSEEK);
}

assert(vf.vi);

pcm_format_t pcmf{ (uint32_t)vf.vi->rate, 16, vf.vi->channels };
auto bits_per_sample = (pcmf.bitdepth / 8) * pcmf.channels;
auto byte_size = (size_t)(sample_length * bits_per_sample);

return std::make_unique<vorbis_part_t>(std::move(vf), pcmf, byte_size);
}
6 changes: 6 additions & 0 deletions thcrap_bgmmod/thcrap_bgmmod.vcxproj
Expand Up @@ -20,10 +20,16 @@
<ImportGroup Label="PropertySheets">
<Import Project="$(SolutionDir)\thcrap.props" />
</ImportGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)libs/libogg/include/;$(SolutionDir)libs/libvorbis/include/;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="src\bgmmod.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="src\vorbis.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\bgmmod.hpp" />
Expand Down

0 comments on commit f6f953d

Please sign in to comment.