Skip to content

Commit

Permalink
Added Library/Snd, using it in AudioPlayer
Browse files Browse the repository at this point in the history
  • Loading branch information
captainurist committed Nov 10, 2023
1 parent 3cb7573 commit 0b5868c
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 106 deletions.
1 change: 1 addition & 0 deletions src/Library/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ add_subdirectory(Platform)
add_subdirectory(Random)
add_subdirectory(Serialization)
add_subdirectory(Snapshots)
add_subdirectory(Snd)
add_subdirectory(Trace)
2 changes: 1 addition & 1 deletion src/Library/Lod/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ set(LIBRARY_LOD_HEADERS
LodWriter.h)

add_library(library_lod STATIC ${LIBRARY_LOD_SOURCES} ${LIBRARY_LOD_HEADERS})
target_link_libraries(library_lod PUBLIC library_serialization library_binary utility)
target_link_libraries(library_lod PUBLIC library_serialization library_binary library_snapshots utility)
target_check_style(library_lod)

if(OE_BUILD_TESTS)
Expand Down
2 changes: 1 addition & 1 deletion src/Library/Lod/LodReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class LodReader final {
void open(Blob blob, std::string_view path, LodOpenFlags openFlags = 0);

/**
* Closes this blob reader & frees all associated resources.
* Closes this LOD reader & frees all associated resources.
*/
void close();

Expand Down
13 changes: 13 additions & 0 deletions src/Library/Snd/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.24 FATAL_ERROR)

set(LIBRARY_SND_SOURCES
SndReader.cpp
SndSnapshots.cpp)

set(LIBRARY_SND_HEADERS
SndReader.h
SndSnapshots.h)

add_library(library_snd STATIC ${LIBRARY_SND_SOURCES} ${LIBRARY_SND_HEADERS})
target_link_libraries(library_snd PUBLIC library_binary library_snapshots library_compression utility)
target_check_style(library_snd)
85 changes: 85 additions & 0 deletions src/Library/Snd/SndReader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include "SndReader.h"

#include <algorithm>
#include <utility>

#include "Library/Compression/Compression.h"
#include "Library/Snapshots/SnapshotSerialization.h"

#include "Utility/Streams/BlobInputStream.h"
#include "Utility/String.h"
#include "Utility/Exception.h"

#include "SndSnapshots.h"

SndReader::SndReader() = default;

SndReader::SndReader(std::string_view path) {
open(path);
}

SndReader::~SndReader() = default;

void SndReader::open(std::string_view path) {
close();

Blob blob = Blob::fromFile(path);
BlobInputStream stream(blob);

std::vector<SndEntry> entries;
deserialize(stream, &entries, tags::via<SndEntry_MM7>);

std::unordered_map<std::string, SndEntry> files;
for (const SndEntry &entry : entries) {
std::string name = toLower(entry.name);
if (files.contains(name))
throw Exception("File '{}' is not a valid SND: contains duplicate entries for '{}'", path, name);

if (entry.offset + entry.size > blob.size())
throw Exception("File '{}' is not a valid SND: entry '{}' points outside the SND file", path, entry.name);

files[name] = std::move(entry);
}

// All good, this is a valid SND, can update `this`.
_snd = std::move(blob);
_path = path;
_files = std::move(files);
}

void SndReader::close() {
// Double-closing is OK.
_snd = Blob();
_path = {};
_files = {};
}

bool SndReader::exists(const std::string &filename) const {
assert(isOpen());

return _files.contains(toLower(filename));
}

Blob SndReader::read(const std::string &filename) const {
assert(isOpen());

const auto pos = _files.find(toLower(filename));
if (pos == _files.cend())
throw Exception("Entry '{}' doesn't exist in SND file '{}'", filename, _path);
const SndEntry &entry = pos->second;

Blob result = _snd.subBlob(entry.offset, entry.size);
if (entry.decompressedSize)
result = zlib::uncompress(result, entry.decompressedSize);
return result;
}

std::vector<std::string> SndReader::ls() const {
assert(isOpen());

std::vector<std::string> result;
for (const auto &[name, _] : _files)
result.push_back(name);
std::sort(result.begin(), result.end());
return result;
}
63 changes: 63 additions & 0 deletions src/Library/Snd/SndReader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#pragma once

#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

#include "Utility/Memory/Blob.h"

struct SndEntry;

/**
* Reader for Might&Magic SND files.
*
* Note that compression is part of the container format in SND files, unlike in LOD, where it's part of the internal
* lod-specific file formats. Thus, we're not exposing it as part of the interface, and there is no 'SndFormats'
* library.
*/
class SndReader {
public:
SndReader();
explicit SndReader(std::string_view path);
~SndReader();

/**
* @param path Path to the SND file to open for reading.
* @throw Exception If the SND couldn't be opened - e.g., if the file doesn't exist,
* or if it's not in SND format.
*/
void open(std::string_view path);

/**
* Closes this SND reader & frees all associated resources.
*/
void close();

[[nodiscard]] bool isOpen() const {
return !!_snd;
}

/**
* @param filename Name of the SND file entry.
* @return Whether the file exists inside the SND. The check is case-insensitive.
*/
[[nodiscard]] bool exists(const std::string &filename) const;

/**
* @param filename Name of the SND file entry.
* @return Contents of the file inside the SND as a `Blob`.
* @throws Exception If file doesn't exist inside the SND.
*/
[[nodiscard]] Blob read(const std::string &filename) const;

/**
* @return List of all files in the SND.
*/
[[nodiscard]] std::vector<std::string> ls() const;

private:
Blob _snd;
std::string _path;
std::unordered_map<std::string, SndEntry> _files;
};
10 changes: 10 additions & 0 deletions src/Library/Snd/SndSnapshots.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include "SndSnapshots.h"

#include "Library/Snapshots/CommonSnapshots.h"

void reconstruct(const SndEntry_MM7 &src, SndEntry *dst) {
reconstruct(src.name, &dst->name);
dst->offset = src.offset;
dst->size = src.size;
dst->decompressedSize = src.decompressedSize;
}
40 changes: 40 additions & 0 deletions src/Library/Snd/SndSnapshots.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#pragma once

#include <cstdint>
#include <array>
#include <string>

#include "Library/Binary/MemCopySerialization.h"

//
// Runtime structs.
//

struct SndEntry {
std::string name;
size_t offset;
size_t size;
size_t decompressedSize;
};


//
// Snapshots.
//

#pragma pack(push, 1)

// Note that there is no SndHeader_MM7. Entries are just stored as a serialized vector in the SND file.

struct SndEntry_MM7 {
std::array<char, 40> name;
uint32_t offset;
uint32_t size;
uint32_t decompressedSize;
};
static_assert(sizeof(SndEntry_MM7) == 52);
MM_DECLARE_MEMCOPY_SERIALIZABLE(SndEntry_MM7)

void reconstruct(const SndEntry_MM7 &src, SndEntry *dst);

#pragma pack(pop)
94 changes: 3 additions & 91 deletions src/Media/Audio/AudioPlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,6 @@ constexpr Pid FAKE_HOUSE_SPEECH_PID = Pid::fromPacked(-6);

extern OpenALSoundProvider *provider;

// TODO(captainurist): doesn't belong here
#pragma pack(push, 1)
struct SoundHeader_mm7 {
char pSoundName[40];
uint32_t uFileOffset;
uint32_t uCompressedSize;
uint32_t uDecompressedSize;
};
#pragma pack(pop)

AudioPlayer::~AudioPlayer() = default;

void AudioPlayer::MusicPlayTrack(MusicID eTrack) {
Expand Down Expand Up @@ -409,31 +399,12 @@ bool AudioPlayer::isWalkingSoundPlays() {
return false;
}

void AudioPlayer::LoadAudioSnd() {
static_assert(sizeof(SoundHeader_mm7) == 52, "Wrong type size");

std::string file_path = makeDataPath("sounds", "audio.snd");
fAudioSnd.open(makeDataPath("sounds", "audio.snd"));

uint32_t uNumSoundHeaders {};
fAudioSnd.readOrFail(&uNumSoundHeaders, sizeof(uNumSoundHeaders));
for (uint32_t i = 0; i < uNumSoundHeaders; i++) {
SoundHeader_mm7 header_mm7;
fAudioSnd.readOrFail(&header_mm7, sizeof(header_mm7));
SoundHeader header;
header.uFileOffset = header_mm7.uFileOffset;
header.uCompressedSize = header_mm7.uCompressedSize;
header.uDecompressedSize = header_mm7.uDecompressedSize;
mSoundHeaders[toLower(header_mm7.pSoundName)] = header;
}
}

void AudioPlayer::Initialize() {
currentMusicTrack = MUSIC_Invalid;
uMasterVolume = 127;

UpdateVolumeFromConfig();
LoadAudioSnd();
_sndReader.open(makeDataPath("sounds", "audio.snd"));

bPlayerReady = true;
}
Expand All @@ -451,72 +422,13 @@ void PlayLevelMusic() {
}
}


bool AudioPlayer::FindSound(const std::string &pName, AudioPlayer::SoundHeader *header) {
if (header == nullptr) {
return false;
}

std::map<std::string, SoundHeader>::iterator it = mSoundHeaders.find(toLower(pName));
if (it == mSoundHeaders.end()) {
return false;
}

*header = it->second;

return true;
}


Blob AudioPlayer::LoadSound(int uSoundID) { // bit of a kludge (load sound by ID index) - plays some interesting files
SoundHeader header = { 0 };

if (uSoundID < 0 || uSoundID > mSoundHeaders.size())
return {};

// iterate through to get sound by int ID
std::map<std::string, SoundHeader>::iterator it = mSoundHeaders.begin();
std::advance(it, uSoundID);

if (it == mSoundHeaders.end())
return {};

header = it->second;

fAudioSnd.seek(header.uFileOffset);
if (header.uCompressedSize >= header.uDecompressedSize) {
header.uCompressedSize = header.uDecompressedSize;
if (header.uDecompressedSize) {
return Blob::read(fAudioSnd, header.uDecompressedSize);
} else {
logger->warning("Can't load sound file!");
return Blob();
}
} else {
return zlib::uncompress(Blob::read(fAudioSnd, header.uCompressedSize), header.uDecompressedSize);
}
}


Blob AudioPlayer::LoadSound(const std::string &pSoundName) {
SoundHeader header = { 0 };
if (!FindSound(pSoundName, &header)) {
if (!_sndReader.exists(pSoundName)) {
logger->warning("AudioPlayer: {} can't load sound header!", pSoundName);
return Blob();
}

fAudioSnd.seek(header.uFileOffset);
if (header.uCompressedSize >= header.uDecompressedSize) {
header.uCompressedSize = header.uDecompressedSize;
if (header.uDecompressedSize) {
return Blob::read(fAudioSnd, header.uDecompressedSize);
} else {
logger->warning("AudioPlayer: {} can't load sound file!", pSoundName);
return Blob();
}
} else {
return zlib::uncompress(Blob::read(fAudioSnd, header.uCompressedSize), header.uDecompressedSize);
}
return _sndReader.read(pSoundName);
}

void AudioPlayer::playSpellSound(SpellId spell, bool is_impact, SoundPlaybackMode mode, Pid pid) {
Expand Down
Loading

0 comments on commit 0b5868c

Please sign in to comment.