RingRacers/src/audio/xmp.cpp
2023-01-04 16:51:12 -06:00

167 lines
3.9 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 "xmp.hpp"
#include <limits>
#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 <size_t C>
Xmp<C>::Xmp() : data_(), instance_(nullptr), module_loaded_(false), looping_(false) {
}
template <size_t C>
Xmp<C>::Xmp(std::vector<std::byte> data)
: data_(std::move(data)), instance_(nullptr), module_loaded_(false), looping_(false) {
_init();
}
template <size_t C>
Xmp<C>::Xmp(tcb::span<std::byte> data)
: data_(data.begin(), data.end()), instance_(nullptr), module_loaded_(false), looping_(false) {
_init();
}
template <size_t C>
Xmp<C>::Xmp(Xmp<C>&& rhs) noexcept : Xmp<C>() {
std::swap(data_, rhs.data_);
std::swap(instance_, rhs.instance_);
std::swap(module_loaded_, rhs.module_loaded_);
std::swap(looping_, rhs.looping_);
}
template <size_t C>
Xmp<C>& Xmp<C>::operator=(Xmp<C>&& 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 <size_t C>
Xmp<C>::~Xmp() {
if (instance_) {
xmp_free_context(instance_);
instance_ = nullptr;
}
}
template <size_t C>
std::size_t Xmp<C>::play_buffer(tcb::span<std::array<int16_t, C>> 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 <size_t C>
void Xmp<C>::reset() {
SRB2_ASSERT(instance_ != nullptr);
SRB2_ASSERT(module_loaded_ == true);
xmp_restart_module(instance_);
}
template <size_t C>
float Xmp<C>::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<float>(info.total_time) / 1000.f;
}
template <size_t C>
void Xmp<C>::seek(int position_ms) {
SRB2_ASSERT(instance_ != nullptr);
SRB2_ASSERT(module_loaded_ == true);
int err = xmp_seek_time(instance_, position_ms);
if (err != 0)
throw XmpException(err);
}
template <size_t C>
void Xmp<C>::_init() {
if (instance_)
return;
if (data_.size() >= std::numeric_limits<long>::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>;