// 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 "ogg.hpp" #include #include "../cxxutil.hpp" using namespace srb2; using namespace srb2::audio; StbVorbisException::StbVorbisException(int code) noexcept : code_(code) { } const char* StbVorbisException::what() const noexcept { switch (code_) { case VORBIS__no_error: return "No error"; case VORBIS_need_more_data: return "Need more data"; case VORBIS_invalid_api_mixing: return "Invalid API mixing"; case VORBIS_outofmem: return "Out of memory"; case VORBIS_feature_not_supported: return "Feature not supported"; case VORBIS_too_many_channels: return "Too many channels"; case VORBIS_file_open_failure: return "File open failure"; case VORBIS_seek_without_length: return "Seek without length"; case VORBIS_unexpected_eof: return "Unexpected EOF"; case VORBIS_seek_invalid: return "Seek invalid"; case VORBIS_invalid_setup: return "Invalid setup"; case VORBIS_invalid_stream: return "Invalid stream"; case VORBIS_missing_capture_pattern: return "Missing capture pattern"; case VORBIS_invalid_stream_structure_version: return "Invalid stream structure version"; case VORBIS_continued_packet_flag_invalid: return "Continued packet flag invalid"; case VORBIS_incorrect_stream_serial_number: return "Incorrect stream serial number"; case VORBIS_invalid_first_page: return "Invalid first page"; case VORBIS_bad_packet_type: return "Bad packet type"; case VORBIS_cant_find_last_page: return "Can't find last page"; case VORBIS_seek_failed: return "Seek failed"; case VORBIS_ogg_skeleton_not_supported: return "OGG skeleton not supported"; default: return "Unrecognized error code"; } } Ogg::Ogg() noexcept : memory_data_(), instance_(nullptr) { } Ogg::Ogg(std::vector data) : memory_data_(std::move(data)), instance_(nullptr) { _init_with_data(); } Ogg::Ogg(tcb::span data) : memory_data_(data.begin(), data.end()), instance_(nullptr) { _init_with_data(); } Ogg::Ogg(Ogg&& rhs) noexcept : memory_data_(), instance_(nullptr) { std::swap(memory_data_, rhs.memory_data_); std::swap(instance_, rhs.instance_); } Ogg& Ogg::operator=(Ogg&& rhs) noexcept { std::swap(memory_data_, rhs.memory_data_); std::swap(instance_, rhs.instance_); return *this; } Ogg::~Ogg() { if (instance_) { stb_vorbis_close(instance_); instance_ = nullptr; } } std::size_t Ogg::get_samples(tcb::span> buffer) { SRB2_ASSERT(instance_ != nullptr); size_t read = stb_vorbis_get_samples_float_interleaved( instance_, 1, reinterpret_cast(buffer.data()), buffer.size() * 1 ); return read; } std::size_t Ogg::get_samples(tcb::span> buffer) { SRB2_ASSERT(instance_ != nullptr); size_t read = stb_vorbis_get_samples_float_interleaved( instance_, 2, reinterpret_cast(buffer.data()), buffer.size() * 2 ); stb_vorbis_info info = stb_vorbis_get_info(instance_); if (info.channels == 1) { for (auto& sample : buffer.subspan(0, read)) { sample.amplitudes[1] = sample.amplitudes[0]; } } return read; } OggComment Ogg::comment() const { SRB2_ASSERT(instance_ != nullptr); stb_vorbis_comment c_comment = stb_vorbis_get_comment(instance_); return OggComment { std::string(c_comment.vendor), std::vector(c_comment.comment_list, c_comment.comment_list + c_comment.comment_list_length)}; } std::size_t Ogg::sample_rate() const { SRB2_ASSERT(instance_ != nullptr); stb_vorbis_info info = stb_vorbis_get_info(instance_); return info.sample_rate; } void Ogg::seek(std::size_t sample) { SRB2_ASSERT(instance_ != nullptr); stb_vorbis_seek(instance_, sample); } std::size_t Ogg::position() const { SRB2_ASSERT(instance_ != nullptr); return stb_vorbis_get_sample_offset(instance_); } float Ogg::position_seconds() const { return position() / static_cast(sample_rate()); } std::size_t Ogg::duration_samples() const { SRB2_ASSERT(instance_ != nullptr); return stb_vorbis_stream_length_in_samples(instance_); } float Ogg::duration_seconds() const { SRB2_ASSERT(instance_ != nullptr); return stb_vorbis_stream_length_in_seconds(instance_); } std::size_t Ogg::channels() const { SRB2_ASSERT(instance_ != nullptr); stb_vorbis_info info = stb_vorbis_get_info(instance_); return info.channels; } void Ogg::_init_with_data() { if (instance_) { return; } if (memory_data_.size() >= std::numeric_limits::max()) throw std::logic_error("Buffer is too large for stb_vorbis"); if (memory_data_.size() == 0) throw std::logic_error("Insufficient data from stream"); int vorbis_result; instance_ = stb_vorbis_open_memory( reinterpret_cast(memory_data_.data()), memory_data_.size(), &vorbis_result, NULL ); if (vorbis_result != VORBIS__no_error) throw StbVorbisException(vorbis_result); }