// DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2024 by Ronald "Eidolon" Kinard // Copyright (C) 2024 by Kart Krew // // 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 "xmp.hpp" #include #include "../cxxutil.hpp" using namespace srb2; using namespace srb2::audio; XmpException::XmpException(int code) : code_(code) { } const char* XmpException::what() const noexcept { switch (code_) { case -XMP_ERROR_INTERNAL: return "XMP_ERROR_INTERNAL"; case -XMP_ERROR_FORMAT: return "XMP_ERROR_FORMAT"; case -XMP_ERROR_LOAD: return "XMP_ERROR_LOAD"; case -XMP_ERROR_DEPACK: return "XMP_ERROR_DEPACK"; case -XMP_ERROR_SYSTEM: return "XMP_ERROR_SYSTEM"; case -XMP_ERROR_INVALID: return "XMP_ERROR_INVALID"; case -XMP_ERROR_STATE: return "XMP_ERROR_STATE"; default: return "unknown"; } } template Xmp::Xmp() : data_(), instance_(nullptr), module_loaded_(false), looping_(false) { } template Xmp::Xmp(std::vector data) : data_(std::move(data)), instance_(nullptr), module_loaded_(false), looping_(false) { _init(); } template Xmp::Xmp(tcb::span data) : data_(data.begin(), data.end()), instance_(nullptr), module_loaded_(false), looping_(false) { _init(); } template Xmp::Xmp(Xmp&& rhs) noexcept : Xmp() { std::swap(data_, rhs.data_); std::swap(instance_, rhs.instance_); std::swap(module_loaded_, rhs.module_loaded_); std::swap(looping_, rhs.looping_); } template Xmp& Xmp::operator=(Xmp&& rhs) noexcept { std::swap(data_, rhs.data_); std::swap(instance_, rhs.instance_); std::swap(module_loaded_, rhs.module_loaded_); std::swap(looping_, rhs.looping_); return *this; }; template Xmp::~Xmp() { if (instance_) { xmp_free_context(instance_); instance_ = nullptr; } } template std::size_t Xmp::play_buffer(tcb::span> buffer) { SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(module_loaded_ == true); int result = xmp_play_buffer(instance_, buffer.data(), buffer.size_bytes(), !looping_); if (result == -XMP_END) return 0; if (result != 0) throw XmpException(result); return buffer.size(); } template void Xmp::reset() { SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(module_loaded_ == true); xmp_restart_module(instance_); } template float Xmp::duration_seconds() const { SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(module_loaded_ == true); xmp_frame_info info; xmp_get_frame_info(instance_, &info); return static_cast(info.total_time) / 1000.f; } template float Xmp::position_seconds() const { SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(module_loaded_ == true); xmp_frame_info info; xmp_get_frame_info(instance_, &info); return static_cast(info.time) / 1000.f; } template void Xmp::seek(int position_ms) { SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(module_loaded_ == true); int pos = xmp_seek_time(instance_, position_ms); if (pos < 0) { throw XmpException(pos); } } template void Xmp::_init() { if (instance_) return; if (data_.size() >= std::numeric_limits::max()) throw std::logic_error("Buffer is too large for xmp"); if (data_.size() == 0) throw std::logic_error("Insufficient data from stream"); instance_ = xmp_create_context(); if (instance_ == nullptr) { throw std::bad_alloc(); } int result = xmp_load_module_from_memory(instance_, data_.data(), data_.size()); if (result != 0) { xmp_free_context(instance_); instance_ = nullptr; throw XmpException(result); } module_loaded_ = true; int flags = 0; if constexpr (C == 1) { flags |= XMP_FORMAT_MONO; } result = xmp_start_player(instance_, 44100, flags); if (result != 0) { xmp_release_module(instance_); module_loaded_ = false; xmp_free_context(instance_); instance_ = nullptr; throw XmpException(result); } } template class srb2::audio::Xmp<1>; template class srb2::audio::Xmp<2>;