RingRacers/src/audio/chunk_load.cpp
2023-01-09 20:02:19 -06:00

236 lines
5.2 KiB
C++

// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "chunk_load.hpp"
#include <stb_vorbis.h>
#include "../cxxutil.hpp"
#include "../io/streams.hpp"
#include "gme.hpp"
#include "gme_player.hpp"
#include "ogg.hpp"
#include "ogg_player.hpp"
#include "resample.hpp"
#include "sound_chunk.hpp"
#include "sound_effect_player.hpp"
#include "wav.hpp"
#include "wav_player.hpp"
using std::nullopt;
using std::optional;
using std::size_t;
using namespace srb2::audio;
using namespace srb2;
namespace
{
// Utility for leveraging Resampler...
class SoundChunkSource : public Source<1>
{
public:
explicit SoundChunkSource(std::unique_ptr<SoundChunk>&& chunk)
: chunk_(std::forward<std::unique_ptr<SoundChunk>>(chunk))
{
}
virtual size_t generate(tcb::span<Sample<1>> buffer) override final
{
if (!chunk_)
return 0;
size_t written = 0;
for (; pos_ < chunk_->samples.size() && written < buffer.size(); pos_++)
{
buffer[written] = chunk_->samples[pos_];
written++;
}
return written;
}
private:
std::unique_ptr<SoundChunk> chunk_;
size_t pos_ {0};
};
template <class I>
std::vector<Sample<1>> generate_to_vec(I& source, std::size_t estimate = 0)
{
std::vector<Sample<1>> generated;
size_t total = 0;
size_t read = 0;
generated.reserve(estimate);
do
{
generated.resize(total + 4096);
read = source.generate(tcb::span {generated.data() + total, 4096});
total += read;
} while (read != 0);
generated.resize(total);
return generated;
}
optional<SoundChunk> try_load_dmx(tcb::span<std::byte> data)
{
io::SpanStream stream {data};
if (io::remaining(stream) < 8)
return nullopt;
uint16_t version = io::read_uint16(stream);
if (version != 3)
return nullopt;
uint16_t rate = io::read_uint16(stream);
uint32_t length = io::read_uint32(stream) - 32u;
if (io::remaining(stream) < (length + 32u))
return nullopt;
stream.seek(io::SeekFrom::kCurrent, 16);
std::vector<Sample<1>> samples;
for (size_t i = 0; i < length; i++)
{
uint8_t doom_sample = io::read_uint8(stream);
float float_sample = audio::sample_to_float(doom_sample);
samples.push_back(Sample<1> {float_sample});
}
size_t samples_len = samples.size();
if (rate == 44100)
{
return SoundChunk {samples};
}
std::unique_ptr<SoundChunkSource> chunk_source =
std::make_unique<SoundChunkSource>(std::make_unique<SoundChunk>(SoundChunk {std::move(samples)}));
Resampler<1> resampler(std::move(chunk_source), rate / static_cast<float>(kSampleRate));
std::vector<Sample<1>> resampled;
size_t total = 0;
size_t read = 0;
resampled.reserve(samples_len * (static_cast<float>(kSampleRate) / rate));
do
{
resampled.resize(total + 4096);
read = resampler.generate(tcb::span {resampled.data() + total, 4096});
total += read;
} while (read != 0);
resampled.resize(total);
return SoundChunk {std::move(resampled)};
}
optional<SoundChunk> try_load_wav(tcb::span<std::byte> data)
{
io::SpanStream stream {data};
audio::Wav wav;
std::size_t sample_rate;
try
{
wav = audio::load_wav(stream);
}
catch (const std::exception& ex)
{
return nullopt;
}
sample_rate = wav.sample_rate();
audio::Resampler<1> resampler(
std::make_unique<WavPlayer>(std::move(wav)),
sample_rate / static_cast<float>(kSampleRate)
);
SoundChunk chunk {generate_to_vec(resampler)};
return chunk;
}
optional<SoundChunk> try_load_ogg(tcb::span<std::byte> data)
{
std::shared_ptr<audio::OggPlayer<1>> player;
try
{
io::SpanStream data_stream {data};
audio::Ogg ogg = audio::load_ogg(data_stream);
player = std::make_shared<audio::OggPlayer<1>>(std::move(ogg));
}
catch (...)
{
return nullopt;
}
player->looping(false);
player->playing(true);
player->reset();
std::size_t sample_rate = player->sample_rate();
audio::Resampler<1> resampler(player, sample_rate / 44100.);
std::vector<Sample<1>> resampled {generate_to_vec(resampler)};
SoundChunk chunk {std::move(resampled)};
return chunk;
}
optional<SoundChunk> try_load_gme(tcb::span<std::byte> data)
{
std::shared_ptr<audio::GmePlayer<1>> player;
try
{
if (data[0] == std::byte {0x1F} && data[1] == std::byte {0x8B})
{
io::SpanStream stream {data};
audio::Gme gme = audio::load_gme(stream);
player = std::make_shared<GmePlayer<1>>(std::move(gme));
}
else
{
io::ZlibInputStream stream {io::SpanStream(data)};
audio::Gme gme = audio::load_gme(stream);
player = std::make_shared<GmePlayer<1>>(std::move(gme));
}
}
catch (...)
{
return nullopt;
}
std::vector<Sample<1>> samples {generate_to_vec(*player)};
SoundChunk chunk {std::move(samples)};
return chunk;
}
} // namespace
optional<SoundChunk> srb2::audio::try_load_chunk(tcb::span<std::byte> data)
{
optional<SoundChunk> ret;
ret = try_load_dmx(data);
if (ret)
return ret;
ret = try_load_wav(data);
if (ret)
return ret;
ret = try_load_ogg(data);
if (ret)
return ret;
ret = try_load_gme(data);
if (ret)
return ret;
return nullopt;
}