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()
|
endforeach()
|
||||||
pop_fnstack()
|
pop_fnstack()
|
||||||
endmacro()
|
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)
|
option(USE_PORTALS "Enable libportal support if available" ON)
|
||||||
set(UI_BACKENDS "")
|
set(UI_BACKENDS "")
|
||||||
list(POP_FRONT 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)
|
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})
|
add_library(liblooper STATIC ${LIBRARY_SOURCES})
|
||||||
target_include_directories(liblooper PUBLIC ${INC})
|
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)
|
macro(add_ui_backend)
|
||||||
set(ARGS ${ARGV})
|
set(ARGS ${ARGV})
|
||||||
list(POP_FRONT ARGS target)
|
list(POP_FRONT ARGS target)
|
||||||
|
@ -147,7 +147,7 @@ if(${ASCLI_EXE} STREQUAL "ASCLIEXE-NOTFOUND")
|
||||||
else()
|
else()
|
||||||
add_test(NAME "verify appstream metadata" COMMAND ${ASCLI_EXE} validate --no-net --pedantic "assets/com.complecwaft.Looper.metainfo.xml")
|
add_test(NAME "verify appstream metadata" COMMAND ${ASCLI_EXE} validate --no-net --pedantic "assets/com.complecwaft.Looper.metainfo.xml")
|
||||||
endif()
|
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(TARGETS looper ${EXTRA_LIBS})
|
||||||
install(FILES assets/icon.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps/)
|
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)
|
install(FILES assets/com.complecwaft.Looper.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
||||||
|
|
202
playback.cpp
202
playback.cpp
|
@ -1,6 +1,12 @@
|
||||||
#include "playback.h"
|
#include "playback.h"
|
||||||
#include "SDL_mixer.h"
|
#include "SDL_mixer.h"
|
||||||
#include <SDL_audio.h>
|
#include <SDL_audio.h>
|
||||||
|
extern "C" {
|
||||||
|
#include <vgmstream.h>
|
||||||
|
#include <util.h>
|
||||||
|
#include <plugins.h>
|
||||||
|
}
|
||||||
|
#include <climits>
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
@ -12,6 +18,55 @@
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include "dbus.hpp"
|
#include "dbus.hpp"
|
||||||
using namespace std::chrono;
|
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) {
|
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;
|
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) {
|
if (st == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
size_t i = 0;
|
|
||||||
size_t max = 0;
|
|
||||||
size_t unit = sizeof(SAMPLETYPE) * spec.channels;
|
size_t unit = sizeof(SAMPLETYPE) * spec.channels;
|
||||||
size_t bytes_per_iter = std::min(((bufsize / unit)) * unit, (size_t)fakespec.size);
|
size_t bytes_per_iter = std::min(((bufsize / unit)) * unit, (size_t)fakespec.size);
|
||||||
while (st->numSamples() < (size_t)len) {
|
while (st->numSamples() < (size_t)len) {
|
||||||
if (general_mixer == nullptr) {
|
if (general_mixer == nullptr && stream == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
general_mixer(nullptr, buf + i, (int)bytes_per_iter);
|
Uint8 *new_buf;
|
||||||
i += bytes_per_iter;
|
int new_bufsize;
|
||||||
max = i + bytes_per_iter;
|
new_buf = buf;
|
||||||
if (max >= bufsize) {
|
new_bufsize = bytes_per_iter;
|
||||||
st->putSamples((SAMPLETYPE*)buf, i/unit);
|
if (this->stream != nullptr) {
|
||||||
i = 0;
|
size_t samples = (int)bytes_per_iter / sizeof(SAMPLETYPE) / this->stream->channels;
|
||||||
max = i + bytes_per_iter;
|
#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);
|
st->receiveSamples((SAMPLETYPE*)stream, len / unit);
|
||||||
|
@ -90,6 +157,52 @@ Mix_Music *PlaybackInstance::Load(const char *file) {
|
||||||
set_signal(PlaybackSignalFileChanged);
|
set_signal(PlaybackSignalFileChanged);
|
||||||
return output;
|
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) {
|
void PlaybackInstance::Unload(Mix_Music *music) {
|
||||||
Mix_HaltMusicStream(music);
|
Mix_HaltMusicStream(music);
|
||||||
Mix_FreeMusic(music);
|
Mix_FreeMusic(music);
|
||||||
|
@ -98,6 +211,14 @@ void PlaybackInstance::Unload(Mix_Music *music) {
|
||||||
current_title = {};
|
current_title = {};
|
||||||
current_file_mutex.unlock();
|
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() {
|
void PlaybackInstance::UpdateST() {
|
||||||
if (speed > 0.0f && speed_changed.exchange(false)) {
|
if (speed > 0.0f && speed_changed.exchange(false)) {
|
||||||
st->setRate(speed);
|
st->setRate(speed);
|
||||||
|
@ -162,6 +283,8 @@ void PlaybackInstance::ThreadFunc() {
|
||||||
double maxSeconds = GetMaxSeconds();
|
double maxSeconds = GetMaxSeconds();
|
||||||
fakespec.size *= maxSeconds;
|
fakespec.size *= maxSeconds;
|
||||||
fakespec.samples *= maxSeconds;
|
fakespec.samples *= maxSeconds;
|
||||||
|
vgmstream_spec = fakespec;
|
||||||
|
vgmstream_spec.format = AUDIO_S16;
|
||||||
size_t new_bufsize = CalculateBufSize(&spec, GetMaxSeconds(), MaxSeconds);
|
size_t new_bufsize = CalculateBufSize(&spec, GetMaxSeconds(), MaxSeconds);
|
||||||
buf = (Uint8*)malloc(new_bufsize);
|
buf = (Uint8*)malloc(new_bufsize);
|
||||||
if (buf == nullptr) {
|
if (buf == nullptr) {
|
||||||
|
@ -175,9 +298,14 @@ void PlaybackInstance::ThreadFunc() {
|
||||||
general_mixer = Mix_GetGeneralMixer();
|
general_mixer = Mix_GetGeneralMixer();
|
||||||
Mix_InitMixer(&fakespec, SDL_FALSE);
|
Mix_InitMixer(&fakespec, SDL_FALSE);
|
||||||
SDL_PauseAudioDevice(device, 0);
|
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;
|
reload = false;
|
||||||
if (music) {
|
if (music || stream) {
|
||||||
playback_ready.store(true);
|
playback_ready.store(true);
|
||||||
} else {
|
} else {
|
||||||
playback_ready.store(false);
|
playback_ready.store(false);
|
||||||
|
@ -185,9 +313,17 @@ void PlaybackInstance::ThreadFunc() {
|
||||||
set_signal(PlaybackSignalStarted);
|
set_signal(PlaybackSignalStarted);
|
||||||
while (running) {
|
while (running) {
|
||||||
if (file_changed.exchange(false)) {
|
if (file_changed.exchange(false)) {
|
||||||
Unload(music);
|
if (stream != nullptr) {
|
||||||
music = Load(filePath.c_str());
|
Unload2(stream);
|
||||||
if (music) {
|
}
|
||||||
|
if (music == nullptr) {
|
||||||
|
Unload(music);
|
||||||
|
}
|
||||||
|
stream = Load2(filePath.c_str());
|
||||||
|
if (stream == nullptr) {
|
||||||
|
music = Load(filePath.c_str());
|
||||||
|
}
|
||||||
|
if (music || stream) {
|
||||||
playback_ready.store(true);
|
playback_ready.store(true);
|
||||||
} else {
|
} else {
|
||||||
playback_ready.store(false);
|
playback_ready.store(false);
|
||||||
|
@ -195,21 +331,39 @@ void PlaybackInstance::ThreadFunc() {
|
||||||
}
|
}
|
||||||
if (flag_mutex.try_lock()) {
|
if (flag_mutex.try_lock()) {
|
||||||
if (seeking.exchange(false)) {
|
if (seeking.exchange(false)) {
|
||||||
Mix_SetMusicPositionStream(music, position);
|
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);
|
set_signal(PlaybackSignalSeeked);
|
||||||
}
|
}
|
||||||
if (pause_changed.exchange(false)) {
|
if (pause_changed.exchange(false)) {
|
||||||
|
if (stream != nullptr) {
|
||||||
|
SDL_PauseAudioDevice(device, paused ? 1 : 0);
|
||||||
|
}
|
||||||
if (paused) {
|
if (paused) {
|
||||||
Mix_PauseMusicStream(music);
|
if (music != nullptr) {
|
||||||
|
Mix_PauseMusicStream(music);
|
||||||
|
}
|
||||||
set_signal(PlaybackSignalPaused);
|
set_signal(PlaybackSignalPaused);
|
||||||
} else {
|
} else {
|
||||||
Mix_ResumeMusicStream(music);
|
if (music != nullptr) {
|
||||||
|
Mix_ResumeMusicStream(music);
|
||||||
|
}
|
||||||
set_signal(PlaybackSignalResumed);
|
set_signal(PlaybackSignalResumed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (update.exchange(false)) {
|
if (update.exchange(false)) {
|
||||||
Mix_VolumeMusicStream(music, (int)(volume / 100.0 * MIX_MAX_VOLUME));
|
|
||||||
SDL_LockAudioDevice(device);
|
SDL_LockAudioDevice(device);
|
||||||
|
real_volume = volume / 100.0;
|
||||||
|
if (stream == nullptr) {
|
||||||
|
Mix_VolumeMusicStream(music, (int)(volume / 100.0 * MIX_MAX_VOLUME));
|
||||||
|
}
|
||||||
UpdateST();
|
UpdateST();
|
||||||
size_t correct_buf_size = CalculateBufSize(&spec, GetMaxSeconds(), MaxSeconds);
|
size_t correct_buf_size = CalculateBufSize(&spec, GetMaxSeconds(), MaxSeconds);
|
||||||
size_t max_buf_size = correct_buf_size * 10;
|
size_t max_buf_size = correct_buf_size * 10;
|
||||||
|
@ -238,7 +392,15 @@ void PlaybackInstance::ThreadFunc() {
|
||||||
}
|
}
|
||||||
flag_mutex.unlock();
|
flag_mutex.unlock();
|
||||||
}
|
}
|
||||||
position = Mix_GetMusicPosition(music);
|
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);
|
std::this_thread::sleep_for(20ms);
|
||||||
}
|
}
|
||||||
playback_ready.store(false);
|
playback_ready.store(false);
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <SDL_mixer.h>
|
#include <SDL_mixer.h>
|
||||||
|
extern "C" {
|
||||||
|
#include <vgmstream.h>
|
||||||
|
}
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <SDL_audio.h>
|
#include <SDL_audio.h>
|
||||||
|
@ -196,12 +199,18 @@ private:
|
||||||
SDL_AudioDeviceID device;
|
SDL_AudioDeviceID device;
|
||||||
SoundTouch *st;
|
SoundTouch *st;
|
||||||
SDL_AudioSpec spec;
|
SDL_AudioSpec spec;
|
||||||
|
SDL_AudioStream *sdl_stream;
|
||||||
/// @brief A fake SDL_AudioSpec used to trick SDL Mixer X into allocating a bigger buffer.
|
/// @brief A fake SDL_AudioSpec used to trick SDL Mixer X into allocating a bigger buffer.
|
||||||
SDL_AudioSpec fakespec;
|
SDL_AudioSpec fakespec;
|
||||||
|
SDL_AudioSpec vgmstream_spec;
|
||||||
void SDLCallbackInner(Uint8 *stream, int len);
|
void SDLCallbackInner(Uint8 *stream, int len);
|
||||||
static void SDLCallback(void *userdata, Uint8 *stream, int len);
|
static void SDLCallback(void *userdata, Uint8 *stream, int len);
|
||||||
Mix_Music *Load(const char* file);
|
Mix_Music *Load(const char* file);
|
||||||
|
VGMSTREAM *Load2(const char *file);
|
||||||
void Unload(Mix_Music* music);
|
void Unload(Mix_Music* music);
|
||||||
|
void Unload2(VGMSTREAM *stream);
|
||||||
|
VGMSTREAM *stream;
|
||||||
|
double real_volume = 1.0;
|
||||||
void ThreadFunc();
|
void ThreadFunc();
|
||||||
void UpdateST();
|
void UpdateST();
|
||||||
double GetMaxSeconds();
|
double GetMaxSeconds();
|
||||||
|
|
Loading…
Reference in a new issue