// 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 #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&& chunk) : chunk_(std::forward>(chunk)) { } virtual size_t generate(tcb::span> 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 chunk_; size_t pos_ {0}; }; template std::vector> generate_to_vec(I& source, std::size_t estimate = 0) { std::vector> 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 try_load_dmx(tcb::span 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> 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 chunk_source = std::make_unique(std::make_unique(SoundChunk {std::move(samples)})); Resampler<1> resampler(std::move(chunk_source), rate / static_cast(kSampleRate)); std::vector> resampled; size_t total = 0; size_t read = 0; resampled.reserve(samples_len * (static_cast(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 try_load_wav(tcb::span 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(std::move(wav)), sample_rate / static_cast(kSampleRate) ); SoundChunk chunk {generate_to_vec(resampler)}; return chunk; } optional try_load_ogg(tcb::span data) { std::shared_ptr> player; try { io::SpanStream data_stream {data}; audio::Ogg ogg = audio::load_ogg(data_stream); player = std::make_shared>(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> resampled {generate_to_vec(resampler)}; SoundChunk chunk {std::move(resampled)}; return chunk; } optional try_load_gme(tcb::span data) { std::shared_ptr> 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>(std::move(gme)); } else { io::ZlibInputStream stream {io::SpanStream(data)}; audio::Gme gme = audio::load_gme(stream); player = std::make_shared>(std::move(gme)); } } catch (...) { return nullopt; } std::vector> samples {generate_to_vec(*player)}; SoundChunk chunk {std::move(samples)}; return chunk; } } // namespace optional srb2::audio::try_load_chunk(tcb::span data) { optional 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; }