757 lines
23 KiB
C++
757 lines
23 KiB
C++
#include "playback.h"
|
|
#include "SDL_mixer.h"
|
|
#include "playback_backend.hpp"
|
|
#include <SDL_audio.h>
|
|
#include <chrono>
|
|
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"
|
|
#include <string.h>
|
|
#include "util.hpp"
|
|
#include "file_backend.hpp"
|
|
using namespace std::chrono;
|
|
|
|
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 (process == nullptr) return;
|
|
size_t unit = sizeof(SAMPLETYPE) * spec.channels;
|
|
size_t bytes_per_iter = (bufsize / unit) * unit;
|
|
SAMPLETYPE *tmpbuf = (SAMPLETYPE*)malloc(bytes_per_iter);
|
|
memset(tmpbuf, 0, bytes_per_iter);
|
|
while (m_st->numSamples() * unit <= (size_t)len) {
|
|
for (auto &data : m_playback_data) {
|
|
Uint8 *new_buf;
|
|
int new_bufsize;
|
|
new_buf = buf;
|
|
bytes_per_iter = data.m_backend_spec.size;
|
|
if (bytes_per_iter > bufsize) {
|
|
bytes_per_iter = bufsize;
|
|
}
|
|
new_bufsize = bytes_per_iter;
|
|
size_t new_bytes = data.m_pProcess->render(buf, bytes_per_iter);
|
|
if (SDL_AudioStreamPut(data.m_pStream, buf, new_bytes) < 0) {
|
|
ERROR.writefln("SDL_AudioStreamPut: %s", SDL_GetError());
|
|
DEBUG.writefln("Current audio backend: %s", data.m_pProcess->get_backend_id().c_str());
|
|
DEBUG.writefln("SDL_AudioStream *sdl_stream = %0lx", (size_t)data.m_pStream);
|
|
return;
|
|
}
|
|
do {
|
|
new_bufsize = SDL_AudioStreamGet(data.m_pStream, buf, bytes_per_iter);
|
|
if (new_bufsize < 0) {
|
|
ERROR.writefln("SDL_AudioStreamGet: %s", SDL_GetError());
|
|
DEBUG.writefln("Current audio backend: %s", data.m_pProcess->get_backend_id().c_str());
|
|
return;
|
|
}
|
|
SAMPLETYPE *sbuf = (SAMPLETYPE*)buf;
|
|
for (size_t i = 0; i < (new_bufsize / unit) * spec.channels; i++) {
|
|
if (data.m_fade_time >= 0.0) {
|
|
if ((i % spec.channels) == 0) {
|
|
data.m_fade_time -= 1.0 / (spec.freq);
|
|
if (data.m_fade_time <= 0.0) {
|
|
data.delete_needed = true;
|
|
break;
|
|
}
|
|
}
|
|
sbuf[i] *= data.m_fade_time;
|
|
}
|
|
tmpbuf[i] += sbuf[i];
|
|
}
|
|
data.m_st->putSamples(sbuf, new_bufsize / unit);
|
|
if (data.delete_needed) {
|
|
break;
|
|
}
|
|
} while (new_bufsize > 0);
|
|
}
|
|
for (auto &data : m_playback_data) {
|
|
size_t tmplen = 0;
|
|
size_t bufused = (bufsize / unit) * unit;
|
|
bool done = false;
|
|
{
|
|
uint samples;
|
|
do {
|
|
samples = data.m_st->receiveSamples((SAMPLETYPE*)(void*)buf, tmplen / unit);
|
|
m_st->putSamples((SAMPLETYPE*)(void*)buf, samples);
|
|
} while (samples > 0);
|
|
}
|
|
for (size_t i = 0; i < len; i += bufused) {
|
|
if (i + bufused >= len) {
|
|
tmplen = len - i;
|
|
} else {
|
|
tmplen = bufused;
|
|
}
|
|
uint samples = m_st->receiveSamples((SAMPLETYPE*)(void*)buf, tmplen / unit);
|
|
for (size_t j = 0; j < samples * spec.channels; j++) {
|
|
((SAMPLETYPE*)(void*)stream)[j] += ((SAMPLETYPE*)(void*)buf)[(i * bufused / unit) + j];
|
|
}
|
|
if (samples == 0) {
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
if (data.delete_needed) {
|
|
continue;
|
|
}
|
|
}
|
|
for (size_t i = 0; i > m_playback_data.size();) {
|
|
auto &data = m_playback_data[i];
|
|
if (data.delete_needed) {
|
|
m_playback_data.erase(m_playback_data.begin() + i);
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#ifdef __ANDROID__
|
|
oboe::DataCallbackResult PlaybackInstance::onAudioReady(
|
|
oboe::AudioStream *audioStream,
|
|
void *audioData,
|
|
int32_t numFrames) {
|
|
audio_playback_mutex.lock();
|
|
if (!audio_playback_paused.load()) {
|
|
SDLCallbackInner((Uint8*)audioData, numFrames * audioStream->getBytesPerFrame());
|
|
} else {
|
|
memset(audioData, 0, numFrames * audioStream->getBytesPerFrame());
|
|
}
|
|
audio_playback_mutex.unlock();
|
|
return oboe::DataCallbackResult::Continue;
|
|
}
|
|
#endif
|
|
void PlaybackInstance::LockAudioDevice() {
|
|
#ifdef USE_SDL
|
|
SDL_LockAudioDevice(device);
|
|
#else
|
|
audio_playback_mutex.lock();
|
|
#endif
|
|
}
|
|
void PlaybackInstance::UnlockAudioDevice() {
|
|
#ifdef USE_SDL
|
|
SDL_UnlockAudioDevice(device);
|
|
#else
|
|
audio_playback_mutex.unlock();
|
|
#endif
|
|
}
|
|
void PlaybackInstance::SDLCallback(void *userdata, Uint8 *stream, int len) {
|
|
((PlaybackInstance*)userdata)->SDLCallbackInner(stream, len);
|
|
}
|
|
void PlaybackInstance::Load(const char *file, int idx) {
|
|
LockAudioDevice();
|
|
load_finished.store(false);
|
|
playback_ready.store(false);
|
|
for (auto &data : m_playback_data) {
|
|
data.m_fade_time = 1.0;
|
|
}
|
|
PlaybackProcess *process = nullptr;
|
|
playback_data data;
|
|
try {
|
|
process = new PlaybackProcess(file, idx);
|
|
} catch (std::exception e) {
|
|
ERROR.writefln("Exception caught when creating process: %s", e.what());
|
|
data.delete_needed = true;
|
|
}
|
|
length = 0.0;
|
|
if (!data.delete_needed && process->process_running()) {
|
|
length = process->get_length();
|
|
auto backend_spec_proxy = process->get_audio_spec();
|
|
SDL_AudioSpec backend_spec;
|
|
memset(&backend_spec, 0, sizeof(data.m_backend_spec));
|
|
backend_spec.channels = backend_spec_proxy->channel_count();
|
|
audio_data_t sample_fmt;
|
|
sample_fmt.size = backend_spec_proxy->bits() / 8;
|
|
sample_fmt.endian = backend_spec_proxy->endian() == EndianID::BIG;
|
|
sample_fmt.is_float = backend_spec_proxy->format_type() == FormatType::FLOAT;
|
|
sample_fmt.is_signed = backend_spec_proxy->format_type() != FormatType::UNSIGNED;
|
|
backend_spec.format = sample_spec_to_sdl(sample_fmt);
|
|
backend_spec.freq = backend_spec_proxy->sample_rate();
|
|
backend_spec.samples = backend_spec_proxy->has_max_samples() ? backend_spec_proxy->max_samples() : backend_spec_proxy->has_min_samples() ? backend_spec_proxy->min_samples() : 0;
|
|
if (backend_spec.samples == 0) backend_spec.samples = 100;
|
|
DEBUG.writeln("Backend audio specification:");
|
|
DEBUG.writefln("\tFormat: %s", sdl_to_str(backend_spec.format).c_str());
|
|
DEBUG.writefln("\tChannels: %d", backend_spec.channels);
|
|
DEBUG.writefln("\tSample rate: %d", backend_spec.freq);
|
|
DEBUG.writefln("\tSamples: %d", backend_spec.samples);
|
|
try {
|
|
data = playback_data(backend_spec, spec, process);
|
|
} catch (CustomException &e) {
|
|
set_error(e.what());
|
|
set_signal(PlaybackSignalErrorOccurred);
|
|
ERROR.writeln(e.what());
|
|
}
|
|
size_t required_size = sample_fmt.size * data.m_backend_spec.samples;
|
|
m_st->setChannels(spec.channels);
|
|
m_st->setRate(spec.freq);
|
|
m_st->flush();
|
|
data.m_backend_spec.size = required_size;
|
|
if (bufsize < required_size || buf == nullptr) {
|
|
bufsize = required_size;
|
|
buf = (Uint8*)((buf == nullptr) ? malloc(bufsize) : realloc(buf, bufsize));
|
|
}
|
|
if (buf == nullptr) {
|
|
ERROR.writeln("Failed to allocate memory for playback!");
|
|
set_error("Failed to allocate memory for playback!");
|
|
set_signal(PlaybackSignalErrorOccurred);
|
|
bufsize = 0;
|
|
}
|
|
delete backend_spec_proxy;
|
|
playback_ready.store(true);
|
|
} else {
|
|
set_error("Failed to create playback backend.");
|
|
set_signal(PlaybackSignalErrorOccurred);
|
|
delete process;
|
|
process = nullptr;
|
|
}
|
|
UnlockAudioDevice();
|
|
load_finished.store(true);
|
|
flag_mutex.lock();
|
|
stopped.store(false);
|
|
paused = false;
|
|
just_started.store(true);
|
|
flag_mutex.unlock();
|
|
}
|
|
void PlaybackInstance::Unload() {
|
|
LockAudioDevice();
|
|
m_playback_data.clear();
|
|
if (buf) free(buf);
|
|
buf = nullptr;
|
|
UnlockAudioDevice();
|
|
}
|
|
void PlaybackInstance::UpdateST() {
|
|
bool any_changed = false;
|
|
if (speed > 0.0f && speed_changed.exchange(false)) {
|
|
any_changed = true;
|
|
m_st->setRate(speed);
|
|
set_signal(PlaybackSignalSpeedChanged);
|
|
}
|
|
if (tempo > 0.0f && tempo_changed.exchange(false)) {
|
|
any_changed = true;
|
|
m_st->setTempo(tempo);
|
|
set_signal(PlaybackSignalTempoChanged);
|
|
}
|
|
if (pitch > 0.0f && pitch_changed.exchange(false)) {
|
|
any_changed = true;
|
|
m_st->setPitch(pitch);
|
|
set_signal(PlaybackSignalPitchChanged);
|
|
}
|
|
if (any_changed && process != nullptr) {
|
|
process->set_rate(m_st->getInputOutputSampleRatio());
|
|
}
|
|
}
|
|
double PlaybackInstance::GetMaxSeconds() {
|
|
return std::max((double)(MaxSpeed * MaxTempo), m_st->getInputOutputSampleRatio());
|
|
}
|
|
void PlaybackInstance::InitLoopFunction() {
|
|
bool reload = false;
|
|
init_audio_data();
|
|
speed_changed.store(true);
|
|
tempo_changed.store(true);
|
|
pitch_changed.store(true);
|
|
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 = SDL_SAMPLE_FMT;
|
|
desired.freq = 48000;
|
|
desired.samples = 400;
|
|
desired.channels = 2;
|
|
desired.callback = PlaybackInstance::SDLCallback;
|
|
desired.userdata = this;
|
|
m_st = new SoundTouch();
|
|
#ifdef USE_SDL
|
|
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;
|
|
loop_started = false;
|
|
return;
|
|
}
|
|
SDL_PauseAudioDevice(device, 0);
|
|
#elif defined(USE_OBOE)
|
|
oboe::AudioStreamBuilder builder;
|
|
builder.setDirection(oboe::Direction::Output);
|
|
builder.setSharingMode(oboe::SharingMode::Shared);
|
|
builder.setPerformanceMode(oboe::PerformanceMode::PowerSaving);
|
|
builder.setFormat(SAMPLE_FMT);
|
|
builder.setDataCallback(this);
|
|
auto res = builder.openStream(ostream);
|
|
if (res != oboe::Result::OK) {
|
|
ERROR.writefln("Error opening audio device.");
|
|
set_error("Failed to open audio device!");
|
|
running = false;
|
|
loop_started = false;
|
|
return;
|
|
}
|
|
obtained = desired;
|
|
obtained.channels = ostream->getChannelCount();
|
|
obtained.freq = ostream->getSampleRate();
|
|
auto bufferFrames = ostream->getBufferSizeInFrames();
|
|
auto bytesPerFrame = ostream->getBytesPerFrame();
|
|
auto bytesPerSample = ostream->getBytesPerSample();
|
|
obtained.size = bufferFrames * bytesPerFrame;
|
|
obtained.samples = obtained.size / bytesPerSample;
|
|
oboe::AudioFormat format = ostream->getFormat();
|
|
DEBUG.writefln("About to start audio stream.\n\
|
|
Format: %s\n\
|
|
Channel count: %u\n\
|
|
Sample rate: %u\n\
|
|
Buffer size (frames): %u\n\
|
|
Bytes per frame: %u\n\
|
|
Total bytes: %u\n\
|
|
Bytes per sample: %u\n\
|
|
Total samples: %u"
|
|
, oboe::convertToText(format)
|
|
, obtained.channels
|
|
, obtained.freq
|
|
, bufferFrames
|
|
, bytesPerFrame
|
|
, obtained.size
|
|
, bytesPerSample
|
|
, obtained.samples
|
|
);
|
|
ostream->requestStart();
|
|
#else
|
|
#error Invalid configuration detected.
|
|
#endif
|
|
spec = obtained;
|
|
m_st->setSampleRate(spec.freq);
|
|
m_st->setChannels(spec.channels);
|
|
UpdateST();
|
|
|
|
reload = false;
|
|
playback_ready.store(false);
|
|
flag_mutex.lock();
|
|
this->position = 0.0;
|
|
seeking.store(false);
|
|
paused = true;
|
|
flag_mutex.unlock();
|
|
playback_ready.store(false);
|
|
length = 0.0;
|
|
m_playback_data.clear();
|
|
bufsize = 0;
|
|
if (buf) free((void*)buf);
|
|
buf = nullptr;
|
|
if (process) delete process;
|
|
process = nullptr;
|
|
stream_changed.store(false);
|
|
file_changed.store(false);
|
|
load_requested.store(false);
|
|
current_file = {};
|
|
current_stream = -1;
|
|
current_title = {};
|
|
seeking.store(false);
|
|
pause_changed.store(false);
|
|
just_stopped.store(true);
|
|
just_started.store(false);
|
|
stopped.store(true);
|
|
set_signal(PlaybackSignalStarted);
|
|
load_finished.store(true);
|
|
}
|
|
void PlaybackInstance::LoopFunction() {
|
|
if (file_changed.exchange(false) || load_requested.exchange(false)) {
|
|
Unload();
|
|
Load(filePath.c_str(), 0);
|
|
if (process && process->process_running()) {
|
|
playback_ready.store(true);
|
|
} else {
|
|
playback_ready.store(false);
|
|
}
|
|
load_finished.store(true);
|
|
just_started.store(true);
|
|
}
|
|
if (stream_changed.exchange(false)) {
|
|
current_file_mutex.lock();
|
|
if (current_file.has_value()) {
|
|
std::string file = current_file.value();
|
|
if (current_stream >= streams.size() || current_stream < 0 ||
|
|
streams[current_stream].name == "" || streams[current_stream].length <= 0) {
|
|
current_stream = 0;
|
|
} else {
|
|
LockAudioDevice();
|
|
if (process && process->process_running()) process->set_stream_idx(current_stream);
|
|
UnlockAudioDevice();
|
|
}
|
|
if (process && process->process_running()) {
|
|
playback_ready.store(true);
|
|
} else {
|
|
playback_ready.store(false);
|
|
}
|
|
}
|
|
current_file_mutex.unlock();
|
|
}
|
|
if (flag_mutex.try_lock()) {
|
|
if (seeking.exchange(false)) {
|
|
if (process && process->process_running()) process->set_position(this->position);
|
|
set_signal(PlaybackSignalSeeked);
|
|
}
|
|
if (pause_changed.exchange(false) || just_stopped.exchange(false) || just_started.exchange(false)) {
|
|
audio_playback_paused.store(stopped.load() || paused);
|
|
#ifdef USE_SDL
|
|
SDL_PauseAudioDevice(device, (audio_playback_paused.load()) ? 1 : 0);
|
|
#endif
|
|
if (paused) {
|
|
set_signal(PlaybackSignalPaused);
|
|
} else {
|
|
set_signal(PlaybackSignalResumed);
|
|
}
|
|
}
|
|
if (update.exchange(false)) {
|
|
LockAudioDevice();
|
|
real_volume = volume / 100.0;
|
|
UpdateST();
|
|
UnlockAudioDevice();
|
|
}
|
|
flag_mutex.unlock();
|
|
}
|
|
if (process) {
|
|
position = process->get_position();
|
|
} else {
|
|
position = 0.0;
|
|
}
|
|
|
|
}
|
|
void PlaybackInstance::Render(std::string opath) {
|
|
output_file_mutex.lock();
|
|
output_file = opath;
|
|
output_file_mutex.unlock();
|
|
}
|
|
void PlaybackInstance::DeinitLoopFunction() {
|
|
playback_ready.store(false);
|
|
// ====
|
|
Unload();
|
|
#ifdef USE_OBOE
|
|
if (ostream && ostream->getState() != oboe::StreamState::Closed) {
|
|
ostream->stop();
|
|
ostream->close();
|
|
}
|
|
ostream.reset();
|
|
#elif defined(USE_SDL)
|
|
SDL_CloseAudioDevice(device);
|
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
#endif
|
|
delete m_st;
|
|
m_st = nullptr;
|
|
if (buf) free(buf);
|
|
current_file_mutex.lock();
|
|
current_file = {};
|
|
current_file_mutex.unlock();
|
|
set_signal(PlaybackSignalStopped);
|
|
}
|
|
void PlaybackInstance::ThreadFunc() {
|
|
#ifdef __linux__
|
|
pthread_setname_np(pthread_self(), "Playback control thread");
|
|
#endif
|
|
start_loop();
|
|
while (running && loop_started) {
|
|
LoopHook();
|
|
std::this_thread::sleep_for(20ms);
|
|
}
|
|
stop_loop();
|
|
}
|
|
|
|
PlaybackInstance::PlaybackInstance() {
|
|
running = false;
|
|
paused = true;
|
|
position = 0;
|
|
length = 0;
|
|
volume = 100.0;
|
|
real_volume = 1.0;
|
|
speed = 1.0;
|
|
pitch = 1.0;
|
|
tempo = 1.0;
|
|
prev_speed = -1.0;
|
|
prev_pitch = -1.0;
|
|
prev_tempo = -1.0;
|
|
buf = nullptr;
|
|
tempo_changed.store(true);
|
|
speed_changed.store(true);
|
|
pitch_changed.store(true);
|
|
current_file = {};
|
|
playback_ready = false;
|
|
bufsize = 0;
|
|
process = nullptr;
|
|
running.store(true);
|
|
#ifdef NO_THREADS
|
|
start_loop();
|
|
#else
|
|
thread = std::thread(&PlaybackInstance::ThreadFunc, this);
|
|
loop_started = true;
|
|
#endif
|
|
while (loop_started && !load_finished.exchange(false)) {
|
|
#ifdef NO_THREADS
|
|
LoopHook();
|
|
#endif
|
|
std::this_thread::sleep_for(20ms);
|
|
}
|
|
load_finished.store(false);
|
|
|
|
}
|
|
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();
|
|
running.store(false);
|
|
#ifdef NO_THREADS
|
|
stop_loop();
|
|
#else
|
|
thread.join();
|
|
#endif
|
|
}
|
|
void PlaybackInstance::Load(std::string filePath) {
|
|
load_finished.store(false);
|
|
this->filePath = filePath;
|
|
INFO.writefln("Loading %s...", filePath.c_str());
|
|
flag_mutex.lock();
|
|
this->position = 0.0;
|
|
seeking.store(true);
|
|
paused = true;
|
|
Update();
|
|
flag_mutex.unlock();
|
|
file_changed.store(true);
|
|
wait([this]() {
|
|
return load_finished.load();
|
|
});
|
|
}
|
|
void PlaybackInstance::Start(std::string filePath, int streamIdx) {
|
|
load_finished.store(false);
|
|
Load(filePath);
|
|
INFO.writefln("Playing %s...", filePath.c_str());
|
|
flag_mutex.lock();
|
|
this->position = 0.0;
|
|
seeking.store(true);
|
|
paused = false;
|
|
current_stream = streamIdx;
|
|
stream_changed.store(true);
|
|
Update();
|
|
flag_mutex.unlock();
|
|
wait([this]() {
|
|
return load_finished.load();
|
|
});
|
|
}
|
|
void PlaybackInstance::play_stream(int idx) {
|
|
flag_mutex.lock();
|
|
this->position = 0.0;
|
|
seeking.store(true);
|
|
paused = false;
|
|
current_stream = idx;
|
|
stream_changed.store(true);
|
|
Update();
|
|
just_started.store(true);
|
|
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() {
|
|
flag_mutex.lock();
|
|
stopped.store(true);
|
|
just_started.store(false);
|
|
just_stopped.store(true);
|
|
flag_mutex.unlock();
|
|
}
|
|
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) {
|
|
#ifdef DBUS_ENABLED
|
|
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;
|
|
}
|
|
#endif
|
|
DEBUG.writeln("Creating new playback instance.");
|
|
return new PlaybackInstance();
|
|
}
|
|
std::vector<PlaybackStream> PlaybackInstance::get_streams() {
|
|
std::vector<PlaybackStream> output;
|
|
for (auto stream : streams) {
|
|
output.push_back(stream);
|
|
}
|
|
return output;
|
|
}
|
|
int PlaybackInstance::get_current_stream() {
|
|
return current_stream;
|
|
}
|
|
|
|
void PlaybackInstance::set_property(std::string path, google::protobuf::Any value) {
|
|
if (this->process != nullptr) {
|
|
this->process->set_property(path, value);
|
|
}
|
|
}
|
|
std::optional<google::protobuf::Any> PlaybackInstance::get_property(std::string path) {
|
|
if (this->process != nullptr) {
|
|
return this->process->get_property(path);
|
|
}
|
|
return {};
|
|
}
|
|
std::optional<google::protobuf::Any> PlaybackInstance::reset_property(std::string path) {
|
|
return {};
|
|
}
|
|
std::vector<Property> PlaybackInstance::get_property_list() {
|
|
if (process == nullptr) return {};
|
|
return process->get_property_list();
|
|
}
|