Add vgmstream support
This commit is contained in:
parent
c4414e6f7e
commit
91b2c5a56d
3 changed files with 194 additions and 23 deletions
|
@ -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)
|
||||
|
|
190
playback.cpp
190
playback.cpp
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue