Add vgmstream support

This commit is contained in:
Zachary Hall 2024-04-10 18:00:19 -07:00
parent c4414e6f7e
commit 91b2c5a56d
3 changed files with 194 additions and 23 deletions

View file

@ -90,7 +90,7 @@ macro(target_pkgconfig)
endforeach()
pop_fnstack()
endmacro()
set(INC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
set(INC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} subprojects/vgmstream/src subprojects/vgmstream/src/base)
option(USE_PORTALS "Enable libportal support if available" ON)
set(UI_BACKENDS "")
list(POP_FRONT UI_BACKENDS)
@ -105,7 +105,7 @@ endmacro()
prefix_all(LIBRARY_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ backend.cpp options.cpp playback.cpp util.cpp log.cpp dbus.cpp)
add_library(liblooper STATIC ${LIBRARY_SOURCES})
target_include_directories(liblooper PUBLIC ${INC})
target_link_libraries(liblooper PUBLIC SDL2::SDL2 ${SDL_MIXER_X_TARGET} PkgConfig::SoundTouch SDBusCpp::sdbus-c++)
target_link_libraries(liblooper PUBLIC SDL2::SDL2 ${SDL_MIXER_X_TARGET} PkgConfig::SoundTouch SDBusCpp::sdbus-c++ libvgmstream libvgmstream_shared)
macro(add_ui_backend)
set(ARGS ${ARGV})
list(POP_FRONT ARGS target)
@ -147,7 +147,7 @@ if(${ASCLI_EXE} STREQUAL "ASCLIEXE-NOTFOUND")
else()
add_test(NAME "verify appstream metadata" COMMAND ${ASCLI_EXE} validate --no-net --pedantic "assets/com.complecwaft.Looper.metainfo.xml")
endif()
target_link_libraries(looper PUBLIC liblooper libvgmstream PkgConfig::jsoncpp ${UI_BACKENDS})
target_link_libraries(looper PUBLIC liblooper PkgConfig::jsoncpp ${UI_BACKENDS})
install(TARGETS looper ${EXTRA_LIBS})
install(FILES assets/icon.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps/)
install(FILES assets/com.complecwaft.Looper.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)

View file

@ -1,6 +1,12 @@
#include "playback.h"
#include "SDL_mixer.h"
#include <SDL_audio.h>
extern "C" {
#include <vgmstream.h>
#include <util.h>
#include <plugins.h>
}
#include <climits>
#include <SDL.h>
#include <exception>
#include <thread>
@ -12,6 +18,55 @@
#include <filesystem>
#include "dbus.hpp"
using namespace std::chrono;
int NotSDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len,
const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len)
{
if (dst_data) {
*dst_data = NULL;
}
if (dst_len) {
*dst_len = 0;
}
if (!src_data) {
return 0;
} else if (src_len < 0) {
return 0;
} else if (!dst_data) {
return 0;
} else if (!dst_len) {
return 0;
}
int retval = -1;
Uint8 *dst = NULL;
int dstlen = 0;
SDL_AudioStream *stream = SDL_NewAudioStream(src_spec->format, src_spec->channels, src_spec->freq, dst_spec->format, dst_spec->channels, dst_spec->freq);
if (stream) {
if ((SDL_AudioStreamPut(stream, src_data, src_len) == 0) && (SDL_AudioStreamFlush(stream) == 0)) {
dstlen = SDL_AudioStreamAvailable(stream);
if (dstlen >= 0) {
dst = (Uint8 *)SDL_malloc(dstlen);
if (dst) {
retval = (SDL_AudioStreamGet(stream, dst, dstlen) >= 0) ? 0 : -1;
}
}
}
}
if (retval == -1) {
SDL_free(dst);
} else {
*dst_data = dst;
*dst_len = dstlen;
}
SDL_FreeAudioStream(stream);
return retval;
}
size_t CalculateBufSize(SDL_AudioSpec *obtained, double seconds, double max_seconds, size_t samples_override = 0) {
return ((((samples_override == 0) ? obtained->samples : samples_override) * std::min(seconds, max_seconds)) + 1) * sizeof(SAMPLETYPE) * obtained->channels;
}
@ -23,21 +78,33 @@ void PlaybackInstance::SDLCallbackInner(Uint8 *stream, int len) {
if (st == nullptr) {
return;
}
size_t i = 0;
size_t max = 0;
size_t unit = sizeof(SAMPLETYPE) * spec.channels;
size_t bytes_per_iter = std::min(((bufsize / unit)) * unit, (size_t)fakespec.size);
while (st->numSamples() < (size_t)len) {
if (general_mixer == nullptr) {
if (general_mixer == nullptr && stream == nullptr) {
return;
}
general_mixer(nullptr, buf + i, (int)bytes_per_iter);
i += bytes_per_iter;
max = i + bytes_per_iter;
if (max >= bufsize) {
st->putSamples((SAMPLETYPE*)buf, i/unit);
i = 0;
max = i + bytes_per_iter;
Uint8 *new_buf;
int new_bufsize;
new_buf = buf;
new_bufsize = bytes_per_iter;
if (this->stream != nullptr) {
size_t samples = (int)bytes_per_iter / sizeof(SAMPLETYPE) / this->stream->channels;
#ifdef SOUNDTOUCH_INTEGER_SAMPLES
samples = render_vgmstream((sample_t*)(buf), (int)samples, this->stream);
#else
samples = render_vgmstream((sample_t*)(buf), (int)samples, this->stream);
vgmstream_spec.samples = samples;
SDL_AudioStreamPut(sdl_stream, buf, samples * sizeof(sample_t) * this->stream->channels);
new_bufsize = SDL_AudioStreamGet(sdl_stream, buf, bufsize);
#endif
for (int i = 0; i < new_bufsize / sizeof(SAMPLETYPE); i++) {
((SAMPLETYPE*)new_buf)[i] *= real_volume;
}
st->putSamples((SAMPLETYPE*)new_buf, new_bufsize / unit);
} else {
general_mixer(nullptr, buf, (int)bytes_per_iter);
st->putSamples((SAMPLETYPE*)new_buf, new_bufsize / unit);
}
}
st->receiveSamples((SAMPLETYPE*)stream, len / unit);
@ -90,6 +157,52 @@ Mix_Music *PlaybackInstance::Load(const char *file) {
set_signal(PlaybackSignalFileChanged);
return output;
}
VGMSTREAM *PlaybackInstance::Load2(const char *file) {
SDL_LockAudioDevice(device);
STREAMFILE *sf = open_stdio_streamfile(file);
if (!sf) {
ERROR.writefln("Could not find file '%s'", file);
SDL_UnlockAudioDevice(device);
return nullptr;
}
auto *output = init_vgmstream_from_STREAMFILE(sf);
sf->stream_index = 0;
close_streamfile(sf);
if (!output) {
DEBUG.writeln("VGMStream init failed.");
SDL_UnlockAudioDevice(device);
return nullptr;
}
//vgmstream_set_loop_target(stream, -1);
vgmstream_cfg_t cfg = {0};
cfg.allow_play_forever = 1;
cfg.play_forever = 1;
cfg.force_loop = 1;
cfg.really_force_loop = 1;
cfg.disable_config_override = 1;
vgmstream_apply_config(output, &cfg);
vgmstream_spec.channels = output->channels;
vgmstream_spec.freq = output->sample_rate;
length = (double)output->loop_end_sample / (double)output->sample_rate;
update.store(true);
current_file_mutex.lock();
current_file = std::string(file);
const char *title_tag = output->stream_name;
// Check for an empty string, which indicates there's no title tag.
if (title_tag[0] == '\0') {
std::filesystem::path path(current_file.value());
current_title = path.stem().string();
} else {
current_title = std::string(title_tag);
}
sdl_stream = SDL_NewAudioStream(vgmstream_spec.format, vgmstream_spec.channels, vgmstream_spec.freq, spec.format, spec.channels, spec.freq);
current_file_mutex.unlock();
set_signal(PlaybackSignalFileChanged);
SDL_UnlockAudioDevice(device);
return output;
}
void PlaybackInstance::Unload(Mix_Music *music) {
Mix_HaltMusicStream(music);
Mix_FreeMusic(music);
@ -98,6 +211,14 @@ void PlaybackInstance::Unload(Mix_Music *music) {
current_title = {};
current_file_mutex.unlock();
}
void PlaybackInstance::Unload2(VGMSTREAM *stream) {
SDL_LockAudioDevice(device);
close_vgmstream(stream);
SDL_FreeAudioStream(sdl_stream);
stream = nullptr;
sdl_stream = nullptr;
SDL_UnlockAudioDevice(device);
}
void PlaybackInstance::UpdateST() {
if (speed > 0.0f && speed_changed.exchange(false)) {
st->setRate(speed);
@ -162,6 +283,8 @@ void PlaybackInstance::ThreadFunc() {
double maxSeconds = GetMaxSeconds();
fakespec.size *= maxSeconds;
fakespec.samples *= maxSeconds;
vgmstream_spec = fakespec;
vgmstream_spec.format = AUDIO_S16;
size_t new_bufsize = CalculateBufSize(&spec, GetMaxSeconds(), MaxSeconds);
buf = (Uint8*)malloc(new_bufsize);
if (buf == nullptr) {
@ -175,9 +298,14 @@ void PlaybackInstance::ThreadFunc() {
general_mixer = Mix_GetGeneralMixer();
Mix_InitMixer(&fakespec, SDL_FALSE);
SDL_PauseAudioDevice(device, 0);
Mix_Music *music = Load(filePath.c_str());
stream = Load2(filePath.c_str());
Mix_Music *music = nullptr;
if (stream == nullptr) {
music = Load(filePath.c_str());
}
reload = false;
if (music) {
if (music || stream) {
playback_ready.store(true);
} else {
playback_ready.store(false);
@ -185,9 +313,17 @@ void PlaybackInstance::ThreadFunc() {
set_signal(PlaybackSignalStarted);
while (running) {
if (file_changed.exchange(false)) {
if (stream != nullptr) {
Unload2(stream);
}
if (music == nullptr) {
Unload(music);
}
stream = Load2(filePath.c_str());
if (stream == nullptr) {
music = Load(filePath.c_str());
if (music) {
}
if (music || stream) {
playback_ready.store(true);
} else {
playback_ready.store(false);
@ -195,21 +331,39 @@ void PlaybackInstance::ThreadFunc() {
}
if (flag_mutex.try_lock()) {
if (seeking.exchange(false)) {
if (stream != nullptr) {
SDL_LockAudioDevice(device);
seek_vgmstream(stream, (int32_t)((double)stream->sample_rate * position));
st->flush();
SDL_UnlockAudioDevice(device);
} else {
Mix_SetMusicPositionStream(music, position);
}
set_signal(PlaybackSignalSeeked);
}
if (pause_changed.exchange(false)) {
if (stream != nullptr) {
SDL_PauseAudioDevice(device, paused ? 1 : 0);
}
if (paused) {
if (music != nullptr) {
Mix_PauseMusicStream(music);
}
set_signal(PlaybackSignalPaused);
} else {
if (music != nullptr) {
Mix_ResumeMusicStream(music);
}
set_signal(PlaybackSignalResumed);
}
}
if (update.exchange(false)) {
Mix_VolumeMusicStream(music, (int)(volume / 100.0 * MIX_MAX_VOLUME));
SDL_LockAudioDevice(device);
real_volume = volume / 100.0;
if (stream == nullptr) {
Mix_VolumeMusicStream(music, (int)(volume / 100.0 * MIX_MAX_VOLUME));
}
UpdateST();
size_t correct_buf_size = CalculateBufSize(&spec, GetMaxSeconds(), MaxSeconds);
size_t max_buf_size = correct_buf_size * 10;
@ -238,7 +392,15 @@ void PlaybackInstance::ThreadFunc() {
}
flag_mutex.unlock();
}
if (stream != nullptr) {
double maybe_new_position = (double)stream->current_sample / stream->sample_rate;
if (position > maybe_new_position) {
position = maybe_new_position;
}
position += 0.02 * (speed * tempo);
} else if (music != nullptr) {
position = Mix_GetMusicPosition(music);
}
std::this_thread::sleep_for(20ms);
}
playback_ready.store(false);

View file

@ -1,5 +1,8 @@
#pragma once
#include <SDL_mixer.h>
extern "C" {
#include <vgmstream.h>
}
#include <thread>
#include <SDL.h>
#include <SDL_audio.h>
@ -196,12 +199,18 @@ private:
SDL_AudioDeviceID device;
SoundTouch *st;
SDL_AudioSpec spec;
SDL_AudioStream *sdl_stream;
/// @brief A fake SDL_AudioSpec used to trick SDL Mixer X into allocating a bigger buffer.
SDL_AudioSpec fakespec;
SDL_AudioSpec vgmstream_spec;
void SDLCallbackInner(Uint8 *stream, int len);
static void SDLCallback(void *userdata, Uint8 *stream, int len);
Mix_Music *Load(const char* file);
VGMSTREAM *Load2(const char *file);
void Unload(Mix_Music* music);
void Unload2(VGMSTREAM *stream);
VGMSTREAM *stream;
double real_volume = 1.0;
void ThreadFunc();
void UpdateST();
double GetMaxSeconds();