looper/playback.cpp

622 lines
No EOL
20 KiB
C++

#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>
#include <cmath>
#ifdef __linux__
#include <pthread.h>
#endif
#include "log.hpp"
#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;
}
void PlaybackInstance::SDLCallbackInner(Uint8 *stream, int len) {
SDL_memset((void*)stream, 0, len);
if (!playback_ready.load()) {
return;
}
if (st == nullptr) {
return;
}
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 && stream == nullptr) {
return;
}
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
size_t new_samples = render_vgmstream((sample_t*)(buf), (int)samples, this->stream);
#else
size_t new_samples = render_vgmstream((sample_t*)(buf), (int)samples, this->stream);
samples = new_samples;
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
if (samples > new_samples) {
reset_vgmstream(this->stream);
}
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);
}
void PlaybackInstance::SDLCallback(void *userdata, Uint8 *stream, int len) {
((PlaybackInstance*)userdata)->SDLCallbackInner(stream, len);
}
Mix_Music *PlaybackInstance::Load(const char *file) {
{
std::filesystem::path fpath = std::string(file);
fpath = fpath.parent_path() / fpath.stem();
std::string fpath_str = fpath.string();
std::vector<std::string> soundfonts = {fpath_str + ".sf2", fpath_str + ".dls"};
std::string sf_path_str = "";
bool any_path_exists = false;
for (auto sf_path : soundfonts) {
if (std::filesystem::exists(sf_path)) {
any_path_exists = true;
sf_path_str += ";" + sf_path;
}
}
if (any_path_exists) {
sf_path_str = sf_path_str.substr(1);
Mix_SetSoundFonts(sf_path_str.c_str());
} else {
Mix_SetSoundFonts(NULL);
}
}
Mix_Music *output = Mix_LoadMUS(file);
if (output == nullptr) {
ERROR.writefln("Error loading music '%s': %s", file, Mix_GetError());
set_error("Error loading music!");
return nullptr;
}
Mix_PlayMusicStream(output, -1);
length = Mix_MusicDuration(output);
update.store(true);
current_file_mutex.lock();
current_file = std::string(file);
const char *title_tag = Mix_GetMusicTitleTag(output);
// 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);
}
current_file_mutex.unlock();
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.disable_config_override = 1;
vgmstream_apply_config(output, &cfg);
vgmstream_spec.channels = output->channels;
vgmstream_spec.freq = output->sample_rate;
length = (double)output->num_samples / (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);
current_file_mutex.lock();
current_file = {};
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);
set_signal(PlaybackSignalSpeedChanged);
}
if (tempo > 0.0f && tempo_changed.exchange(false)) {
st->setTempo(tempo);
set_signal(PlaybackSignalTempoChanged);
}
if (pitch > 0.0f && pitch_changed.exchange(false)) {
st->setPitch(pitch);
set_signal(PlaybackSignalPitchChanged);
}
}
double PlaybackInstance::GetMaxSeconds() {
return std::max((double)(MaxSpeed * MaxTempo), st->getInputOutputSampleRatio());
}
void PlaybackInstance::ThreadFunc() {
#ifdef __linux__
pthread_setname_np(pthread_self(), "Playback control thread");
#endif
bool reload = false;
speed_changed.store(true);
tempo_changed.store(true);
pitch_changed.store(true);
while (running) {
playback_ready.store(false);
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
ERROR.writefln("Error initializing SDL: '%s'", SDL_GetError());
set_error("Failed to initialize SDL!");
return;
}
}
SDL_AudioSpec obtained;
SDL_AudioSpec desired;
desired.format =
#ifdef SOUNDTOUCH_INTEGER_SAMPLES
AUDIO_S16SYS;
#else
AUDIO_F32SYS;
#endif
desired.freq = 48000;
desired.samples = 1024;
desired.channels = 2;
desired.callback = PlaybackInstance::SDLCallback;
desired.userdata = this;
st = new SoundTouch();
Mix_Init(MIX_INIT_FLAC|MIX_INIT_MID|MIX_INIT_MOD|MIX_INIT_MP3|MIX_INIT_OGG|MIX_INIT_OPUS|MIX_INIT_WAVPACK);
if ((device = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE)) == 0) {
ERROR.writefln("Error opening audio device: '%s'", SDL_GetError());
set_error("Failed to open audio device!");
running = false;
break;
}
spec = obtained;
st->setSampleRate(spec.freq);
st->setChannels(spec.channels);
UpdateST();
bufsize = 0;
fakespec = spec;
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) {
ERROR.writeln("Failed to allocate memory for playback!");
set_error("Failed to allocate memory for playback!");
set_signal(PlaybackSignalErrorOccurred);
running = false;
break;
}
bufsize = new_bufsize;
general_mixer = Mix_GetGeneralMixer();
Mix_InitMixer(&fakespec, SDL_FALSE);
SDL_PauseAudioDevice(device, 0);
stream = Load2(filePath.c_str());
Mix_Music *music = nullptr;
if (stream == nullptr) {
music = Load(filePath.c_str());
}
reload = false;
if (music || stream) {
playback_ready.store(true);
} else {
playback_ready.store(false);
}
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 || stream) {
playback_ready.store(true);
} else {
playback_ready.store(false);
}
}
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)) {
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;
bool too_large = max_buf_size < bufsize;
bool too_small = correct_buf_size > bufsize;
if (too_large) {
ERROR.writes("Bufsize is too large - ");
} else if (too_small) {
ERROR.writes("Bufsize is too small - ");
}
if (too_large || too_small) {
ERROR.writeln("Resizing buffer...");
general_mixer = nullptr;
bufsize = 0;
buf = (Uint8*)realloc((void*)buf, correct_buf_size);
if (buf == nullptr) {
ERROR.writes("Failed to allocate memory for playback!");
set_error("Failed to allocate memory for playback!");
set_signal(PlaybackSignalErrorOccurred);
running = false;
break;
}
bufsize = correct_buf_size;
}
SDL_UnlockAudioDevice(device);
}
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);
// ====
Unload(music);
SDL_CloseAudioDevice(device);
Mix_CloseAudio();
Mix_Quit();
SDL_QuitSubSystem(SDL_INIT_AUDIO);
delete st;
free(buf);
}
current_file_mutex.lock();
current_file = {};
current_file_mutex.unlock();
set_signal(PlaybackSignalStopped);
}
PlaybackInstance::PlaybackInstance() {
running = false;
paused = true;
position = 0;
length = 0;
volume = 100.0;
speed = 1.0;
pitch = 1.0;
tempo = 1.0;
tempo_changed.store(true);
speed_changed.store(true);
pitch_changed.store(true);
current_file = {};
playback_ready = false;
bufsize = 0;
}
std::optional<std::string> PlaybackInstance::get_current_file() {
current_file_mutex.lock();
std::optional<std::string> output = current_file;
current_file_mutex.unlock();
return output;
}
std::optional<std::string> PlaybackInstance::get_current_title() {
current_file_mutex.lock();
std::optional<std::string> output = current_title;
current_file_mutex.unlock();
return output;
}
PlaybackInstance::~PlaybackInstance() {
Stop();
}
void PlaybackInstance::Start(std::string filePath) {
this->filePath = filePath;
INFO.writefln("Playing %s...", filePath.c_str());
flag_mutex.lock();
this->position = 0.0;
seeking.store(true);
paused = false;
Update();
if (running.exchange(true)) {
file_changed.store(true);
} else {
thread = std::thread(&PlaybackInstance::ThreadFunc, this);
}
flag_mutex.unlock();
}
double PlaybackInstance::GetPosition() {
return position;
}
double PlaybackInstance::GetLength() {
return length;
}
void PlaybackInstance::Seek(double position) {
flag_mutex.lock();
this->position = position;
seeking.store(true);
flag_mutex.unlock();
}
void PlaybackInstance::Pause() {
flag_mutex.lock();
paused = !paused;
pause_changed.store(true);
flag_mutex.unlock();
}
bool PlaybackInstance::IsPaused() {
return paused;
}
void PlaybackInstance::Stop() {
if (running.exchange(false)) {
thread.join();
}
}
void PlaybackInstance::Update() {
if (prev_pitch != pitch) {
pitch_changed.store(true);
}
if (prev_speed != speed) {
speed_changed.store(true);
}
if (prev_tempo != tempo) {
tempo_changed.store(true);
}
update.store(true);
}
bool PlaybackInstance::IsStopped() {
return !running;
}
void Playback::set_signal(uint16_t signal) {
signal_mutex.lock();
for (auto &kv : signals_occurred) {
kv.second |= signal;
}
signal_mutex.unlock();
}
uint16_t Playback::handle_signals(uint16_t signals, void *handle) {
if (signal_mutex.try_lock()) {
if (!signals_occurred.contains(handle)) {
signals_occurred[handle] = PlaybackSignalNone;
}
uint16_t output = signals_occurred[handle];
signals_occurred[handle] &= ~output;
signal_mutex.unlock();
return output;
} else {
return PlaybackSignalNone;
}
}
void Playback::register_handle(void *handle) {
signal_mutex.lock();
if (!signals_occurred.contains(handle)) {
signals_occurred[handle] = PlaybackSignalNone;
}
signal_mutex.unlock();
error_mutex.lock();
if (!errors_occurred.contains(handle)) {
std::deque<std::string> new_value;
for (size_t i = 0; i < errors.size(); i++) {
new_value.push_back(errors[i]);
}
errors_occurred[handle] = new_value;
}
error_mutex.unlock();
}
void Playback::unregister_handle(void *handle) {
signal_mutex.lock();
if (signals_occurred.contains(handle)) {
signals_occurred.erase(handle);
}
signal_mutex.unlock();
error_mutex.lock();
if (errors_occurred.contains(handle)) {
errors_occurred.erase(handle);
}
error_mutex.unlock();
}
void PlaybackInstance::SetTempo(float tempo) {
this->tempo = tempo;
Update();
}
void PlaybackInstance::SetPitch(float pitch) {
this->pitch = pitch;
Update();
}
void PlaybackInstance::SetSpeed(float speed) {
this->speed = speed;
Update();
}
void PlaybackInstance::SetVolume(float volume) {
this->volume = volume;
Update();
}
float PlaybackInstance::GetTempo() {
return tempo;
}
float PlaybackInstance::GetPitch() {
return pitch;
}
float PlaybackInstance::GetSpeed() {
return speed;
}
float PlaybackInstance::GetVolume() {
return volume;
}
void Playback::set_error(std::string desc) {
error_mutex.lock();
errors.push_back(desc);
for (auto &kv : errors_occurred) {
kv.second.push_front(desc);
}
error_mutex.unlock();
set_signal(PlaybackSignalErrorOccurred);
}
Playback *Playback::Create(bool *daemon_found, bool daemon) {
auto *dbus_proxy = DBusAPISender::Create();
if (dbus_proxy != nullptr) {
if (daemon_found != nullptr) {
*daemon_found = dbus_proxy->IsDaemon();
}
DEBUG.writefln("DBus proxy daemon found: %s", *daemon_found ? "true" : "false");
if (daemon) {
delete dbus_proxy;
return nullptr;
} else {
return dbus_proxy;
}
}
if (daemon_found != nullptr) {
*daemon_found = false;
}
DEBUG.writeln("Creating new playback instance.");
return new PlaybackInstance();
}